mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solutions] Add PLI authorisation for Cases Connector (#161343)
## Summary * Create a new capability called `cases_connectors` which will control the access to the cases connector feature. Note that for users to have access to this feature they also need to be authorized for cases feature and actions feature. * Create a new API tag `casesGetConnectorsConfigure` to restrict access to the Get Connectors APIs. ## Authorization For the authorization of users we use a) a new UI capability b) a new API access tag and c) the existing Cases RBAC. The Cases feature privilege in Security solution is constructed based on the configuration provided by the security serverless plugin. The UI capability, the API tag, and the cases operations will be added/removed depending on the configuration. ### UI capability We include the `CASES_CONNECTORS_CAPABILITY` which will be used by the UI to show/hide various UI components responsible for the case connectors feature. ### APIs There are two APIs that use connectors in Cases. The [Get Connectors API](https://www.elastic.co/guide/en/kibana/current/case-apis.html#findCaseConnectors) which returns all supported connectors by Cases and the [Push Case API](https://www.elastic.co/guide/en/kibana/current/case-apis.html#pushCaseDefaultSpace) that push a case to an external service. #### Get Connectors API The Get Connectors API does not interact with any of the cases' saved objects. It uses the `actionsClient`, provided by the actions plugin, to get all connectors and filter out the ones supported by cases. For that reason, an API tag called `GET_CONNECTORS_CONFIGURE_API_TAG` is added to the API to control access. If the user has access to any of the Cases kibana privilege features (Security, Observability, or Stack) it will have access to the API. This is an expected behavior and in the Security serverless project, only one Case feature will be available. #### Push Case API The Push Case API already authorizes users by using the Cases RBAC. The user should have the `push` operation set in the Cases Kibana feature privilege to be able to use the API. ## Permissions <meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-d1fea174-7fff-4f03-ed2e-9fc3ad3ed789"><div dir="ltr" style="margin-left:0pt;" align="left"> Cases | Actions | Case Connectors | Outcome -- | -- | -- | -- read | all | all | See the connector but cannot edit (current behavior) read | all | none | Hide the connectors in Cases read | read | all | See the connector but cannot edit (current behavior) read | read | none | Hide the connectors in Cases all | all | all | Full access all | all | none | Hide the connectors in Cases all | read | all | See the connector but cannot edit (current behavior) all | read | none | Hide the connectors in Cases </div><br /></b> When the Actions is set to `none` all connector features are hidden ### How to test it? #### ESS * Run ESS and check if it still works as expected for all combinations of cases and actions permissions. #### Serverless * Run Serverless with security essentials (serverless.security.yml) and check if it works as expected for all combinations of cases and actions permissions. ``` xpack.serverless.security.productTypes: [ { product_line: 'security', product_tier: 'essentials' } ] ``` * Run Serverless with security complete (config/serverless.security.yml) and check if it works as expected for all combinations of cases and actions permissions. ``` xpack.serverless.security.productTypes: [ { product_line: 'security', product_tier: 'complete' }, ] ``` ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co>
This commit is contained in:
parent
527c2d5884
commit
aa42bccd40
41 changed files with 478 additions and 96 deletions
|
@ -158,6 +158,7 @@ export const READ_CASES_CAPABILITY = 'read_cases' as const;
|
||||||
export const UPDATE_CASES_CAPABILITY = 'update_cases' as const;
|
export const UPDATE_CASES_CAPABILITY = 'update_cases' as const;
|
||||||
export const DELETE_CASES_CAPABILITY = 'delete_cases' as const;
|
export const DELETE_CASES_CAPABILITY = 'delete_cases' as const;
|
||||||
export const PUSH_CASES_CAPABILITY = 'push_cases' as const;
|
export const PUSH_CASES_CAPABILITY = 'push_cases' as const;
|
||||||
|
export const CASES_CONNECTORS_CAPABILITY = 'cases_connectors' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cases API Tags
|
* Cases API Tags
|
||||||
|
@ -173,6 +174,11 @@ export const SUGGEST_USER_PROFILES_API_TAG = 'casesSuggestUserProfiles';
|
||||||
*/
|
*/
|
||||||
export const BULK_GET_USER_PROFILES_API_TAG = 'bulkGetUserProfiles';
|
export const BULK_GET_USER_PROFILES_API_TAG = 'bulkGetUserProfiles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tag is registered for the connectors (configure) get API
|
||||||
|
*/
|
||||||
|
export const GET_CONNECTORS_CONFIGURE_API_TAG = 'casesGetConnectorsConfigure';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User profiles
|
* User profiles
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,7 +12,8 @@ import type {
|
||||||
READ_CASES_CAPABILITY,
|
READ_CASES_CAPABILITY,
|
||||||
UPDATE_CASES_CAPABILITY,
|
UPDATE_CASES_CAPABILITY,
|
||||||
} from '..';
|
} from '..';
|
||||||
import type { PUSH_CASES_CAPABILITY } from '../constants';
|
import type { CaseMetricsFeature, CasesMetricsResponse, SingleCaseMetricsResponse } from '../api';
|
||||||
|
import type { CASES_CONNECTORS_CAPABILITY, PUSH_CASES_CAPABILITY } from '../constants';
|
||||||
import type { SnakeToCamelCase } from '../types';
|
import type { SnakeToCamelCase } from '../types';
|
||||||
import type {
|
import type {
|
||||||
CaseSeverity,
|
CaseSeverity,
|
||||||
|
@ -285,6 +286,7 @@ export interface CasesPermissions {
|
||||||
update: boolean;
|
update: boolean;
|
||||||
delete: boolean;
|
delete: boolean;
|
||||||
push: boolean;
|
push: boolean;
|
||||||
|
connectors: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CasesCapabilities {
|
export interface CasesCapabilities {
|
||||||
|
@ -293,4 +295,5 @@ export interface CasesCapabilities {
|
||||||
[UPDATE_CASES_CAPABILITY]: boolean;
|
[UPDATE_CASES_CAPABILITY]: boolean;
|
||||||
[DELETE_CASES_CAPABILITY]: boolean;
|
[DELETE_CASES_CAPABILITY]: boolean;
|
||||||
[PUSH_CASES_CAPABILITY]: boolean;
|
[PUSH_CASES_CAPABILITY]: boolean;
|
||||||
|
[CASES_CONNECTORS_CAPABILITY]: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ Object {
|
||||||
"all": Array [
|
"all": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"casesFilesCasesCreate",
|
"casesFilesCasesCreate",
|
||||||
"casesFilesCasesRead",
|
"casesFilesCasesRead",
|
||||||
],
|
],
|
||||||
|
@ -14,6 +15,7 @@ Object {
|
||||||
"read": Array [
|
"read": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"casesFilesCasesRead",
|
"casesFilesCasesRead",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -24,6 +26,7 @@ Object {
|
||||||
"all": Array [
|
"all": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"observabilityFilesCasesCreate",
|
"observabilityFilesCasesCreate",
|
||||||
"observabilityFilesCasesRead",
|
"observabilityFilesCasesRead",
|
||||||
],
|
],
|
||||||
|
@ -33,6 +36,7 @@ Object {
|
||||||
"read": Array [
|
"read": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"observabilityFilesCasesRead",
|
"observabilityFilesCasesRead",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -43,6 +47,7 @@ Object {
|
||||||
"all": Array [
|
"all": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"securitySolutionFilesCasesCreate",
|
"securitySolutionFilesCasesCreate",
|
||||||
"securitySolutionFilesCasesRead",
|
"securitySolutionFilesCasesRead",
|
||||||
],
|
],
|
||||||
|
@ -52,6 +57,7 @@ Object {
|
||||||
"read": Array [
|
"read": Array [
|
||||||
"casesSuggestUserProfiles",
|
"casesSuggestUserProfiles",
|
||||||
"bulkGetUserProfiles",
|
"bulkGetUserProfiles",
|
||||||
|
"casesGetConnectorsConfigure",
|
||||||
"securitySolutionFilesCasesRead",
|
"securitySolutionFilesCasesRead",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,11 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { BULK_GET_USER_PROFILES_API_TAG, SUGGEST_USER_PROFILES_API_TAG } from '../constants';
|
import {
|
||||||
|
BULK_GET_USER_PROFILES_API_TAG,
|
||||||
|
GET_CONNECTORS_CONFIGURE_API_TAG,
|
||||||
|
SUGGEST_USER_PROFILES_API_TAG,
|
||||||
|
} from '../constants';
|
||||||
import { HttpApiTagOperation } from '../constants/types';
|
import { HttpApiTagOperation } from '../constants/types';
|
||||||
import type { Owner } from '../constants/types';
|
import type { Owner } from '../constants/types';
|
||||||
import { constructFilesHttpOperationTag } from '../files';
|
import { constructFilesHttpOperationTag } from '../files';
|
||||||
|
@ -16,8 +20,19 @@ export const getApiTags = (owner: Owner) => {
|
||||||
const read = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Read);
|
const read = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Read);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
all: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, create, read] as const,
|
all: [
|
||||||
read: [SUGGEST_USER_PROFILES_API_TAG, BULK_GET_USER_PROFILES_API_TAG, read] as const,
|
SUGGEST_USER_PROFILES_API_TAG,
|
||||||
|
BULK_GET_USER_PROFILES_API_TAG,
|
||||||
|
GET_CONNECTORS_CONFIGURE_API_TAG,
|
||||||
|
create,
|
||||||
|
read,
|
||||||
|
] as const,
|
||||||
|
read: [
|
||||||
|
SUGGEST_USER_PROFILES_API_TAG,
|
||||||
|
BULK_GET_USER_PROFILES_API_TAG,
|
||||||
|
GET_CONNECTORS_CONFIGURE_API_TAG,
|
||||||
|
read,
|
||||||
|
] as const,
|
||||||
delete: [deleteTag] as const,
|
delete: [deleteTag] as const,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
CASES_CONNECTORS_CAPABILITY,
|
||||||
CREATE_CASES_CAPABILITY,
|
CREATE_CASES_CAPABILITY,
|
||||||
DELETE_CASES_CAPABILITY,
|
DELETE_CASES_CAPABILITY,
|
||||||
PUSH_CASES_CAPABILITY,
|
PUSH_CASES_CAPABILITY,
|
||||||
|
@ -23,7 +24,8 @@ export const createUICapabilities = () => ({
|
||||||
READ_CASES_CAPABILITY,
|
READ_CASES_CAPABILITY,
|
||||||
UPDATE_CASES_CAPABILITY,
|
UPDATE_CASES_CAPABILITY,
|
||||||
PUSH_CASES_CAPABILITY,
|
PUSH_CASES_CAPABILITY,
|
||||||
|
CASES_CONNECTORS_CAPABILITY,
|
||||||
] as const,
|
] as const,
|
||||||
read: [READ_CASES_CAPABILITY] as const,
|
read: [READ_CASES_CAPABILITY, CASES_CONNECTORS_CAPABILITY] as const,
|
||||||
delete: [DELETE_CASES_CAPABILITY] as const,
|
delete: [DELETE_CASES_CAPABILITY] as const,
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,8 +11,8 @@ import {
|
||||||
allCasesPermissions,
|
allCasesPermissions,
|
||||||
noCasesCapabilities,
|
noCasesCapabilities,
|
||||||
noCasesPermissions,
|
noCasesPermissions,
|
||||||
readCasesCapabilities,
|
|
||||||
readCasesPermissions,
|
readCasesPermissions,
|
||||||
|
readCasesCapabilities,
|
||||||
writeCasesCapabilities,
|
writeCasesCapabilities,
|
||||||
writeCasesPermissions,
|
writeCasesPermissions,
|
||||||
} from '../../common/mock';
|
} from '../../common/mock';
|
||||||
|
@ -77,6 +77,12 @@ const hasSecurityWriteAndObservabilityRead: CasesCapabilities = {
|
||||||
generalCases: noCasesCapabilities(),
|
generalCases: noCasesCapabilities(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hasSecurityConnectors: CasesCapabilities = {
|
||||||
|
securitySolutionCases: readCasesCapabilities(),
|
||||||
|
observabilityCases: noCasesCapabilities(),
|
||||||
|
generalCases: noCasesCapabilities(),
|
||||||
|
};
|
||||||
|
|
||||||
describe('canUseCases', () => {
|
describe('canUseCases', () => {
|
||||||
it.each([hasAll, hasSecurity, hasObservability, hasSecurityWriteAndObservabilityRead])(
|
it.each([hasAll, hasSecurity, hasObservability, hasSecurityWriteAndObservabilityRead])(
|
||||||
'returns true for all permissions, if a user has access to both on any solution',
|
'returns true for all permissions, if a user has access to both on any solution',
|
||||||
|
@ -109,4 +115,12 @@ describe('canUseCases', () => {
|
||||||
expect(permissions).toStrictEqual(noCasesPermissions());
|
expect(permissions).toStrictEqual(noCasesPermissions());
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it.each([hasSecurityConnectors])(
|
||||||
|
'returns true for only connectors, if a user has access to only connectors on any solution',
|
||||||
|
(capability) => {
|
||||||
|
const permissions = canUseCases(capability)();
|
||||||
|
expect(permissions).toStrictEqual(readCasesPermissions());
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,8 +40,10 @@ export const canUseCases =
|
||||||
acc.update = acc.update || userCapabilitiesForOwner.update;
|
acc.update = acc.update || userCapabilitiesForOwner.update;
|
||||||
acc.delete = acc.delete || userCapabilitiesForOwner.delete;
|
acc.delete = acc.delete || userCapabilitiesForOwner.delete;
|
||||||
acc.push = acc.push || userCapabilitiesForOwner.push;
|
acc.push = acc.push || userCapabilitiesForOwner.push;
|
||||||
const allFromAcc = acc.create && acc.read && acc.update && acc.delete && acc.push;
|
const allFromAcc =
|
||||||
|
acc.create && acc.read && acc.update && acc.delete && acc.push && acc.connectors;
|
||||||
acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc;
|
acc.all = acc.all || userCapabilitiesForOwner.all || allFromAcc;
|
||||||
|
acc.connectors = acc.connectors || userCapabilitiesForOwner.connectors;
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
|
@ -52,6 +54,7 @@ export const canUseCases =
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe('getUICapabilities', () => {
|
||||||
expect(getUICapabilities(undefined)).toMatchInlineSnapshot(`
|
expect(getUICapabilities(undefined)).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": false,
|
"create": false,
|
||||||
"delete": false,
|
"delete": false,
|
||||||
"push": false,
|
"push": false,
|
||||||
|
@ -25,6 +26,7 @@ describe('getUICapabilities', () => {
|
||||||
expect(getUICapabilities()).toMatchInlineSnapshot(`
|
expect(getUICapabilities()).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": false,
|
"create": false,
|
||||||
"delete": false,
|
"delete": false,
|
||||||
"push": false,
|
"push": false,
|
||||||
|
@ -38,6 +40,7 @@ describe('getUICapabilities', () => {
|
||||||
expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(`
|
expect(getUICapabilities({ create_cases: true })).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": true,
|
"create": true,
|
||||||
"delete": false,
|
"delete": false,
|
||||||
"push": false,
|
"push": false,
|
||||||
|
@ -55,10 +58,12 @@ describe('getUICapabilities', () => {
|
||||||
update_cases: false,
|
update_cases: false,
|
||||||
delete_cases: false,
|
delete_cases: false,
|
||||||
push_cases: false,
|
push_cases: false,
|
||||||
|
cases_connectors: false,
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": false,
|
"create": false,
|
||||||
"delete": false,
|
"delete": false,
|
||||||
"push": false,
|
"push": false,
|
||||||
|
@ -72,6 +77,7 @@ describe('getUICapabilities', () => {
|
||||||
expect(getUICapabilities({})).toMatchInlineSnapshot(`
|
expect(getUICapabilities({})).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": false,
|
"create": false,
|
||||||
"delete": false,
|
"delete": false,
|
||||||
"push": false,
|
"push": false,
|
||||||
|
@ -89,10 +95,35 @@ describe('getUICapabilities', () => {
|
||||||
update_cases: true,
|
update_cases: true,
|
||||||
delete_cases: true,
|
delete_cases: true,
|
||||||
push_cases: true,
|
push_cases: true,
|
||||||
|
cases_connectors: true,
|
||||||
})
|
})
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"all": false,
|
"all": false,
|
||||||
|
"connectors": true,
|
||||||
|
"create": false,
|
||||||
|
"delete": true,
|
||||||
|
"push": true,
|
||||||
|
"read": true,
|
||||||
|
"update": true,
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false for the all field when cases_connectors is false', () => {
|
||||||
|
expect(
|
||||||
|
getUICapabilities({
|
||||||
|
create_cases: false,
|
||||||
|
read_cases: true,
|
||||||
|
update_cases: true,
|
||||||
|
delete_cases: true,
|
||||||
|
push_cases: true,
|
||||||
|
cases_connectors: false,
|
||||||
|
})
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"all": false,
|
||||||
|
"connectors": false,
|
||||||
"create": false,
|
"create": false,
|
||||||
"delete": true,
|
"delete": true,
|
||||||
"push": true,
|
"push": true,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import type { CasesPermissions } from '../../../common';
|
import type { CasesPermissions } from '../../../common';
|
||||||
import {
|
import {
|
||||||
|
CASES_CONNECTORS_CAPABILITY,
|
||||||
CREATE_CASES_CAPABILITY,
|
CREATE_CASES_CAPABILITY,
|
||||||
DELETE_CASES_CAPABILITY,
|
DELETE_CASES_CAPABILITY,
|
||||||
PUSH_CASES_CAPABILITY,
|
PUSH_CASES_CAPABILITY,
|
||||||
|
@ -22,7 +23,8 @@ export const getUICapabilities = (
|
||||||
const update = !!featureCapabilities?.[UPDATE_CASES_CAPABILITY];
|
const update = !!featureCapabilities?.[UPDATE_CASES_CAPABILITY];
|
||||||
const deletePriv = !!featureCapabilities?.[DELETE_CASES_CAPABILITY];
|
const deletePriv = !!featureCapabilities?.[DELETE_CASES_CAPABILITY];
|
||||||
const push = !!featureCapabilities?.[PUSH_CASES_CAPABILITY];
|
const push = !!featureCapabilities?.[PUSH_CASES_CAPABILITY];
|
||||||
const all = create && read && update && deletePriv && push;
|
const connectors = !!featureCapabilities?.[CASES_CONNECTORS_CAPABILITY];
|
||||||
|
const all = create && read && update && deletePriv && push && connectors;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
all,
|
all,
|
||||||
|
@ -31,5 +33,6 @@ export const getUICapabilities = (
|
||||||
update,
|
update,
|
||||||
delete: deletePriv,
|
delete: deletePriv,
|
||||||
push,
|
push,
|
||||||
|
connectors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -193,6 +193,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => {
|
||||||
update: permissions.update,
|
update: permissions.update,
|
||||||
delete: permissions.delete,
|
delete: permissions.delete,
|
||||||
push: permissions.push,
|
push: permissions.push,
|
||||||
|
connectors: permissions.connectors,
|
||||||
},
|
},
|
||||||
visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show },
|
visualize: { crud: !!capabilities.visualize?.save, read: !!capabilities.visualize?.show },
|
||||||
dashboard: {
|
dashboard: {
|
||||||
|
@ -213,6 +214,7 @@ export const useApplicationCapabilities = (): UseApplicationCapabilities => {
|
||||||
permissions.update,
|
permissions.update,
|
||||||
permissions.delete,
|
permissions.delete,
|
||||||
permissions.push,
|
permissions.push,
|
||||||
|
permissions.connectors,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -74,6 +74,7 @@ export const createStartServicesMock = ({ license }: StartServiceArgs = {}): Sta
|
||||||
update_cases: true,
|
update_cases: true,
|
||||||
delete_cases: true,
|
delete_cases: true,
|
||||||
push_cases: true,
|
push_cases: true,
|
||||||
|
cases_connectors: true,
|
||||||
},
|
},
|
||||||
visualize: { save: true, show: true },
|
visualize: { save: true, show: true },
|
||||||
dashboard: { show: true, createNew: true },
|
dashboard: { show: true, createNew: true },
|
||||||
|
|
|
@ -9,9 +9,23 @@ import type { CasesCapabilities, CasesPermissions } from '../../containers/types
|
||||||
|
|
||||||
export const allCasesPermissions = () => buildCasesPermissions();
|
export const allCasesPermissions = () => buildCasesPermissions();
|
||||||
export const noCasesPermissions = () =>
|
export const noCasesPermissions = () =>
|
||||||
buildCasesPermissions({ read: false, create: false, update: false, delete: false, push: false });
|
buildCasesPermissions({
|
||||||
|
read: false,
|
||||||
|
create: false,
|
||||||
|
update: false,
|
||||||
|
delete: false,
|
||||||
|
push: false,
|
||||||
|
connectors: false,
|
||||||
|
});
|
||||||
export const readCasesPermissions = () =>
|
export const readCasesPermissions = () =>
|
||||||
buildCasesPermissions({ read: true, create: false, update: false, delete: false, push: false });
|
buildCasesPermissions({
|
||||||
|
read: true,
|
||||||
|
create: false,
|
||||||
|
update: false,
|
||||||
|
delete: false,
|
||||||
|
push: false,
|
||||||
|
connectors: true,
|
||||||
|
});
|
||||||
export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false });
|
export const noCreateCasesPermissions = () => buildCasesPermissions({ create: false });
|
||||||
export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false });
|
export const noUpdateCasesPermissions = () => buildCasesPermissions({ update: false });
|
||||||
export const noPushCasesPermissions = () => buildCasesPermissions({ push: false });
|
export const noPushCasesPermissions = () => buildCasesPermissions({ push: false });
|
||||||
|
@ -19,6 +33,7 @@ export const noDeleteCasesPermissions = () => buildCasesPermissions({ delete: fa
|
||||||
export const writeCasesPermissions = () => buildCasesPermissions({ read: false });
|
export const writeCasesPermissions = () => buildCasesPermissions({ read: false });
|
||||||
export const onlyDeleteCasesPermission = () =>
|
export const onlyDeleteCasesPermission = () =>
|
||||||
buildCasesPermissions({ read: false, create: false, update: false, delete: true, push: false });
|
buildCasesPermissions({ read: false, create: false, update: false, delete: true, push: false });
|
||||||
|
export const noConnectorsCasePermission = () => buildCasesPermissions({ connectors: false });
|
||||||
|
|
||||||
export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions, 'all'>> = {}) => {
|
export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions, 'all'>> = {}) => {
|
||||||
const create = overrides.create ?? true;
|
const create = overrides.create ?? true;
|
||||||
|
@ -26,6 +41,7 @@ export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions,
|
||||||
const update = overrides.update ?? true;
|
const update = overrides.update ?? true;
|
||||||
const deletePermissions = overrides.delete ?? true;
|
const deletePermissions = overrides.delete ?? true;
|
||||||
const push = overrides.push ?? true;
|
const push = overrides.push ?? true;
|
||||||
|
const connectors = overrides.connectors ?? true;
|
||||||
const all = create && read && update && deletePermissions && push;
|
const all = create && read && update && deletePermissions && push;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -35,6 +51,7 @@ export const buildCasesPermissions = (overrides: Partial<Omit<CasesPermissions,
|
||||||
update,
|
update,
|
||||||
delete: deletePermissions,
|
delete: deletePermissions,
|
||||||
push,
|
push,
|
||||||
|
connectors,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,6 +63,7 @@ export const noCasesCapabilities = () =>
|
||||||
update_cases: false,
|
update_cases: false,
|
||||||
delete_cases: false,
|
delete_cases: false,
|
||||||
push_cases: false,
|
push_cases: false,
|
||||||
|
cases_connectors: false,
|
||||||
});
|
});
|
||||||
export const readCasesCapabilities = () =>
|
export const readCasesCapabilities = () =>
|
||||||
buildCasesCapabilities({
|
buildCasesCapabilities({
|
||||||
|
@ -67,5 +85,6 @@ export const buildCasesCapabilities = (overrides?: Partial<CasesCapabilities>) =
|
||||||
update_cases: overrides?.update_cases ?? true,
|
update_cases: overrides?.update_cases ?? true,
|
||||||
delete_cases: overrides?.delete_cases ?? true,
|
delete_cases: overrides?.delete_cases ?? true,
|
||||||
push_cases: overrides?.push_cases ?? true,
|
push_cases: overrides?.push_cases ?? true,
|
||||||
|
cases_connectors: overrides?.cases_connectors ?? true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,8 +12,12 @@ import { render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
import type { Props } from './connectors';
|
import type { Props } from './connectors';
|
||||||
import { Connectors } from './connectors';
|
import { Connectors } from './connectors';
|
||||||
import type { AppMockRenderer } from '../../common/mock';
|
import {
|
||||||
import { createAppMockRenderer, TestProviders } from '../../common/mock';
|
type AppMockRenderer,
|
||||||
|
noConnectorsCasePermission,
|
||||||
|
createAppMockRenderer,
|
||||||
|
TestProviders,
|
||||||
|
} from '../../common/mock';
|
||||||
import { ConnectorsDropdown } from './connectors_dropdown';
|
import { ConnectorsDropdown } from './connectors_dropdown';
|
||||||
import { connectors, actionTypes } from './__mock__';
|
import { connectors, actionTypes } from './__mock__';
|
||||||
import { ConnectorTypes } from '../../../common/types/domain';
|
import { ConnectorTypes } from '../../../common/types/domain';
|
||||||
|
@ -161,4 +165,14 @@ describe('Connectors', () => {
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
|
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the actions permission message if the user does not have access to case connector', async () => {
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(<Connectors {...props} />);
|
||||||
|
expect(
|
||||||
|
result.getByTestId('configure-case-connector-permissions-error-msg')
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(result.queryByTestId('case-connectors-dropdown')).toBe(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { ConnectorTypes } from '../../../common/types/domain';
|
||||||
import { DeprecatedCallout } from '../connectors/deprecated_callout';
|
import { DeprecatedCallout } from '../connectors/deprecated_callout';
|
||||||
import { isDeprecatedConnector } from '../utils';
|
import { isDeprecatedConnector } from '../utils';
|
||||||
import { useApplicationCapabilities } from '../../common/lib/kibana';
|
import { useApplicationCapabilities } from '../../common/lib/kibana';
|
||||||
|
import { useCasesContext } from '../cases_context/use_cases_context';
|
||||||
|
|
||||||
const EuiFormRowExtended = styled(EuiFormRow)`
|
const EuiFormRowExtended = styled(EuiFormRow)`
|
||||||
.euiFormRow__labelWrapper {
|
.euiFormRow__labelWrapper {
|
||||||
|
@ -63,6 +64,8 @@ const ConnectorsComponent: React.FC<Props> = ({
|
||||||
() => connectors.find((c) => c.id === selectedConnector.id),
|
() => connectors.find((c) => c.id === selectedConnector.id),
|
||||||
[connectors, selectedConnector.id]
|
[connectors, selectedConnector.id]
|
||||||
);
|
);
|
||||||
|
const { permissions } = useCasesContext();
|
||||||
|
const canUseConnectors = permissions.connectors && actions.read;
|
||||||
|
|
||||||
const connectorsName = connector?.name ?? 'none';
|
const connectorsName = connector?.name ?? 'none';
|
||||||
|
|
||||||
|
@ -105,7 +108,7 @@ const ConnectorsComponent: React.FC<Props> = ({
|
||||||
>
|
>
|
||||||
<EuiFlexGroup direction="column">
|
<EuiFlexGroup direction="column">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
{actions.read ? (
|
{canUseConnectors ? (
|
||||||
<ConnectorsDropdown
|
<ConnectorsDropdown
|
||||||
connectors={connectors}
|
connectors={connectors}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -22,7 +22,11 @@ import { incidentTypes, severity, choices } from '../connectors/mock';
|
||||||
import type { FormProps } from './schema';
|
import type { FormProps } from './schema';
|
||||||
import { schema } from './schema';
|
import { schema } from './schema';
|
||||||
import type { AppMockRenderer } from '../../common/mock';
|
import type { AppMockRenderer } from '../../common/mock';
|
||||||
import { createAppMockRenderer, TestProviders } from '../../common/mock';
|
import {
|
||||||
|
noConnectorsCasePermission,
|
||||||
|
createAppMockRenderer,
|
||||||
|
TestProviders,
|
||||||
|
} from '../../common/mock';
|
||||||
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
import { useCaseConfigure } from '../../containers/configure/use_configure';
|
||||||
import { useCaseConfigureResponse } from '../configure_cases/__mock__';
|
import { useCaseConfigureResponse } from '../configure_cases/__mock__';
|
||||||
|
|
||||||
|
@ -190,4 +194,16 @@ describe('Connector', () => {
|
||||||
expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument();
|
expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument();
|
||||||
expect(result.queryByTestId('caseConnectors')).toBe(null);
|
expect(result.queryByTestId('caseConnectors')).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows the actions permission message if the user does not have access to case connector', async () => {
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(
|
||||||
|
<MockHookWrapperComponent>
|
||||||
|
<Connector {...defaultProps} />
|
||||||
|
</MockHookWrapperComponent>
|
||||||
|
);
|
||||||
|
expect(result.getByTestId('create-case-connector-permissions-error-msg')).toBeInTheDocument();
|
||||||
|
expect(result.queryByTestId('caseConnectors')).toBe(null);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { useCaseConfigure } from '../../containers/configure/use_configure';
|
||||||
import { getConnectorById, getConnectorsFormValidators } from '../utils';
|
import { getConnectorById, getConnectorsFormValidators } from '../utils';
|
||||||
import { useApplicationCapabilities } from '../../common/lib/kibana';
|
import { useApplicationCapabilities } from '../../common/lib/kibana';
|
||||||
import * as i18n from '../../common/translations';
|
import * as i18n from '../../common/translations';
|
||||||
|
import { useCasesContext } from '../cases_context/use_cases_context';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
connectors: ActionConnector[];
|
connectors: ActionConnector[];
|
||||||
|
@ -30,6 +31,8 @@ const ConnectorComponent: React.FC<Props> = ({ connectors, isLoading, isLoadingC
|
||||||
const connector = getConnectorById(connectorId, connectors) ?? null;
|
const connector = getConnectorById(connectorId, connectors) ?? null;
|
||||||
const { connector: configurationConnector } = useCaseConfigure();
|
const { connector: configurationConnector } = useCaseConfigure();
|
||||||
const { actions } = useApplicationCapabilities();
|
const { actions } = useApplicationCapabilities();
|
||||||
|
const { permissions } = useCasesContext();
|
||||||
|
const hasReadPermissions = permissions.connectors && actions.read;
|
||||||
|
|
||||||
const defaultConnectorId = useMemo(() => {
|
const defaultConnectorId = useMemo(() => {
|
||||||
return connectors.some((c) => c.id === configurationConnector.id)
|
return connectors.some((c) => c.id === configurationConnector.id)
|
||||||
|
@ -42,7 +45,7 @@ const ConnectorComponent: React.FC<Props> = ({ connectors, isLoading, isLoadingC
|
||||||
connectors,
|
connectors,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!actions.read) {
|
if (!hasReadPermissions) {
|
||||||
return (
|
return (
|
||||||
<EuiText data-test-subj="create-case-connector-permissions-error-msg" size="s">
|
<EuiText data-test-subj="create-case-connector-permissions-error-msg" size="s">
|
||||||
<span>{i18n.READ_ACTIONS_PERMISSIONS_ERROR_MSG}</span>
|
<span>{i18n.READ_ACTIONS_PERMISSIONS_ERROR_MSG}</span>
|
||||||
|
|
|
@ -11,12 +11,14 @@ import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
import type { EditConnectorProps } from '.';
|
import type { EditConnectorProps } from '.';
|
||||||
import { EditConnector } from '.';
|
import { EditConnector } from '.';
|
||||||
import type { AppMockRenderer } from '../../common/mock';
|
|
||||||
import {
|
import {
|
||||||
|
type AppMockRenderer,
|
||||||
createAppMockRenderer,
|
createAppMockRenderer,
|
||||||
readCasesPermissions,
|
readCasesPermissions,
|
||||||
noPushCasesPermissions,
|
noPushCasesPermissions,
|
||||||
TestProviders,
|
TestProviders,
|
||||||
|
noConnectorsCasePermission,
|
||||||
} from '../../common/mock';
|
} from '../../common/mock';
|
||||||
import { basicCase, connectorsMock } from '../../containers/mock';
|
import { basicCase, connectorsMock } from '../../containers/mock';
|
||||||
import { getCaseConnectorsMockResponse } from '../../common/mock/connectors';
|
import { getCaseConnectorsMockResponse } from '../../common/mock/connectors';
|
||||||
|
@ -274,6 +276,17 @@ describe('EditConnector ', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show the callout if the user does not have access to cases connectors', async () => {
|
||||||
|
const props = { ...defaultProps, connectors: [] };
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(<EditConnector {...props} />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.getByTestId('edit-connector-permissions-error-msg')).toBeInTheDocument();
|
||||||
|
expect(result.queryByTestId('push-callouts')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the connectors previewer if the user does not have read access to actions', async () => {
|
it('does not show the connectors previewer if the user does not have read access to actions', async () => {
|
||||||
const props = { ...defaultProps, connectors: [] };
|
const props = { ...defaultProps, connectors: [] };
|
||||||
appMockRender.coreStart.application.capabilities = {
|
appMockRender.coreStart.application.capabilities = {
|
||||||
|
@ -285,6 +298,14 @@ describe('EditConnector ', () => {
|
||||||
expect(result.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
|
expect(result.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show the connectors previewer if the user does not have access to cases connectors', async () => {
|
||||||
|
const props = { ...defaultProps, connectors: [] };
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(<EditConnector {...props} />);
|
||||||
|
expect(result.queryByTestId('connector-fields-preview')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the connectors form if the user does not have read access to actions', async () => {
|
it('does not show the connectors form if the user does not have read access to actions', async () => {
|
||||||
const props = { ...defaultProps, connectors: [] };
|
const props = { ...defaultProps, connectors: [] };
|
||||||
appMockRender.coreStart.application.capabilities = {
|
appMockRender.coreStart.application.capabilities = {
|
||||||
|
@ -296,6 +317,14 @@ describe('EditConnector ', () => {
|
||||||
expect(result.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
|
expect(result.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show the connectors form if the user does not have access to cases connectors', async () => {
|
||||||
|
const props = { ...defaultProps, connectors: [] };
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(<EditConnector {...props} />);
|
||||||
|
expect(result.queryByTestId('edit-connector-fields-form-flex-item')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the push button if the user does not have read access to actions', async () => {
|
it('does not show the push button if the user does not have read access to actions', async () => {
|
||||||
appMockRender.coreStart.application.capabilities = {
|
appMockRender.coreStart.application.capabilities = {
|
||||||
...appMockRender.coreStart.application.capabilities,
|
...appMockRender.coreStart.application.capabilities,
|
||||||
|
@ -317,6 +346,15 @@ describe('EditConnector ', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show the push button if the user does not have access to cases actions', async () => {
|
||||||
|
appMockRender = createAppMockRenderer({ permissions: noConnectorsCasePermission() });
|
||||||
|
|
||||||
|
const result = appMockRender.render(<EditConnector {...defaultProps} />);
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(result.queryByTestId('push-to-external-service')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the edit connectors pencil if the user does not have read access to actions', async () => {
|
it('does not show the edit connectors pencil if the user does not have read access to actions', async () => {
|
||||||
const props = { ...defaultProps, connectors: [] };
|
const props = { ...defaultProps, connectors: [] };
|
||||||
appMockRender.coreStart.application.capabilities = {
|
appMockRender.coreStart.application.capabilities = {
|
||||||
|
@ -332,6 +370,20 @@ describe('EditConnector ', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not show the edit connectors pencil if the user does not have access to case connectors', async () => {
|
||||||
|
const props = { ...defaultProps, connectors: [] };
|
||||||
|
appMockRender = createAppMockRenderer({
|
||||||
|
permissions: noConnectorsCasePermission(),
|
||||||
|
});
|
||||||
|
|
||||||
|
appMockRender.render(<EditConnector {...props} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('connector-edit-header')).toBeInTheDocument();
|
||||||
|
expect(screen.queryByTestId('connector-edit-button')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('does not show the edit connectors pencil if the user does not have push permissions', async () => {
|
it('does not show the edit connectors pencil if the user does not have push permissions', async () => {
|
||||||
const props = { ...defaultProps, connectors: [] };
|
const props = { ...defaultProps, connectors: [] };
|
||||||
appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() });
|
appMockRender = createAppMockRenderer({ permissions: noPushCasesPermissions() });
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { PushButton } from './push_button';
|
||||||
import { PushCallouts } from './push_callouts';
|
import { PushCallouts } from './push_callouts';
|
||||||
import { ConnectorsForm } from './connectors_form';
|
import { ConnectorsForm } from './connectors_form';
|
||||||
import { ConnectorFieldsPreviewForm } from '../connectors/fields_preview_form';
|
import { ConnectorFieldsPreviewForm } from '../connectors/fields_preview_form';
|
||||||
|
import { useCasesContext } from '../cases_context/use_cases_context';
|
||||||
|
|
||||||
export interface EditConnectorProps {
|
export interface EditConnectorProps {
|
||||||
caseData: CaseUI;
|
caseData: CaseUI;
|
||||||
|
@ -45,7 +46,8 @@ export const EditConnector = React.memo(
|
||||||
const [isEdit, setIsEdit] = useState(false);
|
const [isEdit, setIsEdit] = useState(false);
|
||||||
|
|
||||||
const { actions } = useApplicationCapabilities();
|
const { actions } = useApplicationCapabilities();
|
||||||
const hasActionsReadPermissions = actions.read;
|
const { permissions } = useCasesContext();
|
||||||
|
const canUseConnectors = permissions.connectors && actions.read;
|
||||||
|
|
||||||
const onEditClick = useCallback(() => setIsEdit(true), []);
|
const onEditClick = useCallback(() => setIsEdit(true), []);
|
||||||
const onCancelConnector = useCallback(() => setIsEdit(false), []);
|
const onCancelConnector = useCallback(() => setIsEdit(false), []);
|
||||||
|
@ -102,7 +104,7 @@ export const EditConnector = React.memo(
|
||||||
<EuiFlexItem grow={false} data-test-subj="connector-edit-header">
|
<EuiFlexItem grow={false} data-test-subj="connector-edit-header">
|
||||||
<h4>{i18n.CONNECTORS}</h4>
|
<h4>{i18n.CONNECTORS}</h4>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
{!isLoading && !isEdit && hasPushPermissions && hasActionsReadPermissions ? (
|
{!isLoading && !isEdit && hasPushPermissions && canUseConnectors ? (
|
||||||
<EuiFlexItem data-test-subj="connector-edit" grow={false}>
|
<EuiFlexItem data-test-subj="connector-edit" grow={false}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
data-test-subj="connector-edit-button"
|
data-test-subj="connector-edit-button"
|
||||||
|
@ -115,7 +117,7 @@ export const EditConnector = React.memo(
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
<EuiHorizontalRule margin="xs" />
|
<EuiHorizontalRule margin="xs" />
|
||||||
<EuiFlexGroup data-test-subj="edit-connectors" direction="column" alignItems="stretch">
|
<EuiFlexGroup data-test-subj="edit-connectors" direction="column" alignItems="stretch">
|
||||||
{!isLoading && !isEdit && hasErrorMessages && hasActionsReadPermissions && (
|
{!isLoading && !isEdit && hasErrorMessages && canUseConnectors && (
|
||||||
<EuiFlexItem data-test-subj="push-callouts">
|
<EuiFlexItem data-test-subj="push-callouts">
|
||||||
<PushCallouts
|
<PushCallouts
|
||||||
errorsMsg={errorsMsg}
|
errorsMsg={errorsMsg}
|
||||||
|
@ -125,18 +127,18 @@ export const EditConnector = React.memo(
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
)}
|
)}
|
||||||
{!hasActionsReadPermissions && (
|
{!canUseConnectors && (
|
||||||
<EuiText data-test-subj="edit-connector-permissions-error-msg" size="s">
|
<EuiText data-test-subj="edit-connector-permissions-error-msg" size="s">
|
||||||
<span>{i18n.READ_ACTIONS_PERMISSIONS_ERROR_MSG}</span>
|
<span>{i18n.READ_ACTIONS_PERMISSIONS_ERROR_MSG}</span>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
)}
|
)}
|
||||||
{hasActionsReadPermissions && !isEdit && (
|
{canUseConnectors && !isEdit && (
|
||||||
<ConnectorFieldsPreviewForm
|
<ConnectorFieldsPreviewForm
|
||||||
connector={caseActionConnector}
|
connector={caseActionConnector}
|
||||||
fields={caseConnectorFields}
|
fields={caseConnectorFields}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasActionsReadPermissions && isEdit && (
|
{canUseConnectors && isEdit && (
|
||||||
<ConnectorsForm
|
<ConnectorsForm
|
||||||
caseData={caseData}
|
caseData={caseData}
|
||||||
caseConnectors={caseConnectors}
|
caseConnectors={caseConnectors}
|
||||||
|
@ -146,25 +148,21 @@ export const EditConnector = React.memo(
|
||||||
onSubmit={onSubmitConnector}
|
onSubmit={onSubmitConnector}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!hasErrorMessages &&
|
{!hasErrorMessages && !isLoading && !isEdit && hasPushPermissions && canUseConnectors && (
|
||||||
!isLoading &&
|
<EuiFlexItem grow={false}>
|
||||||
!isEdit &&
|
<span>
|
||||||
hasPushPermissions &&
|
<PushButton
|
||||||
hasActionsReadPermissions && (
|
hasBeenPushed={hasBeenPushed}
|
||||||
<EuiFlexItem grow={false}>
|
disabled={disablePushButton}
|
||||||
<span>
|
isLoading={isLoadingPushToService}
|
||||||
<PushButton
|
pushToService={handlePushToService}
|
||||||
hasBeenPushed={hasBeenPushed}
|
errorsMsg={errorsMsg}
|
||||||
disabled={disablePushButton}
|
showTooltip={errorsMsg.length > 0 || !needsToBePushed || !hasPushPermissions}
|
||||||
isLoading={isLoadingPushToService}
|
connectorName={connectorWithName.name}
|
||||||
pushToService={handlePushToService}
|
/>
|
||||||
errorsMsg={errorsMsg}
|
</span>
|
||||||
showTooltip={errorsMsg.length > 0 || !needsToBePushed || !hasPushPermissions}
|
</EuiFlexItem>
|
||||||
connectorName={connectorWithName.name}
|
)}
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</EuiFlexItem>
|
|
||||||
)}
|
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiText>
|
</EuiText>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -11,14 +11,17 @@ import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
import { casesQueriesKeys } from '../constants';
|
import { casesQueriesKeys } from '../constants';
|
||||||
import type { ServerError } from '../../types';
|
import type { ServerError } from '../../types';
|
||||||
|
import { useCasesContext } from '../../components/cases_context/use_cases_context';
|
||||||
|
|
||||||
export function useGetSupportedActionConnectors() {
|
export function useGetSupportedActionConnectors() {
|
||||||
const toasts = useToasts();
|
const toasts = useToasts();
|
||||||
const { actions } = useApplicationCapabilities();
|
const { actions } = useApplicationCapabilities();
|
||||||
|
const { permissions } = useCasesContext();
|
||||||
|
|
||||||
return useQuery(
|
return useQuery(
|
||||||
casesQueriesKeys.connectorsList(),
|
casesQueriesKeys.connectorsList(),
|
||||||
async ({ signal }) => {
|
async ({ signal }) => {
|
||||||
if (!actions.read) {
|
if (!actions.read || !permissions.connectors) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return getSupportedActionConnectors({ signal });
|
return getSupportedActionConnectors({ signal });
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { renderHook } from '@testing-library/react-hooks';
|
import { renderHook } from '@testing-library/react-hooks';
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
import { TestProviders } from '../../common/mock';
|
import { noConnectorsCasePermission, TestProviders } from '../../common/mock';
|
||||||
import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana';
|
import { useApplicationCapabilities, useToasts } from '../../common/lib/kibana';
|
||||||
import { useGetSupportedActionConnectors } from './use_get_supported_action_connectors';
|
import { useGetSupportedActionConnectors } from './use_get_supported_action_connectors';
|
||||||
|
|
||||||
|
@ -65,4 +65,20 @@ describe('useConnectors', () => {
|
||||||
expect(spyOnFetchConnectors).not.toHaveBeenCalled();
|
expect(spyOnFetchConnectors).not.toHaveBeenCalled();
|
||||||
expect(result.current.data).toEqual([]);
|
expect(result.current.data).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not fetch connectors when the user does not has access to connectors', async () => {
|
||||||
|
const spyOnFetchConnectors = jest.spyOn(api, 'getSupportedActionConnectors');
|
||||||
|
useApplicationCapabilitiesMock().actions = { crud: true, read: true };
|
||||||
|
|
||||||
|
const { result, waitForNextUpdate } = renderHook(() => useGetSupportedActionConnectors(), {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<TestProviders permissions={noConnectorsCasePermission()}>{children}</TestProviders>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForNextUpdate();
|
||||||
|
|
||||||
|
expect(spyOnFetchConnectors).not.toHaveBeenCalled();
|
||||||
|
expect(result.current.data).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2458,7 +2458,7 @@ Object {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"message": "Failed attempt to update cases [id=1] as owner \\"awesome\\"",
|
"message": "Failed attempt to push cases [id=1] as owner \\"awesome\\"",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2478,7 +2478,7 @@ Object {
|
||||||
"change",
|
"change",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"message": "Failed attempt to update a case as any owners",
|
"message": "Failed attempt to push a case as any owners",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2500,7 +2500,7 @@ Object {
|
||||||
"type": "cases",
|
"type": "cases",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"message": "User is updating cases [id=5] as owner \\"super\\"",
|
"message": "User is pushing cases [id=5] as owner \\"super\\"",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2516,7 +2516,7 @@ Object {
|
||||||
"change",
|
"change",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"message": "User is updating a case as any owners",
|
"message": "User is pushing a case as any owners",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,12 @@ const updateVerbs: Verbs = {
|
||||||
past: 'updated',
|
past: 'updated',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pushVerbs: Verbs = {
|
||||||
|
present: 'push',
|
||||||
|
progressive: 'pushing',
|
||||||
|
past: 'pushed',
|
||||||
|
};
|
||||||
|
|
||||||
const deleteVerbs: Verbs = {
|
const deleteVerbs: Verbs = {
|
||||||
present: 'delete',
|
present: 'delete',
|
||||||
progressive: 'deleting',
|
progressive: 'deleting',
|
||||||
|
@ -164,7 +170,7 @@ const CaseOperations = {
|
||||||
ecsType: EVENT_TYPES.change,
|
ecsType: EVENT_TYPES.change,
|
||||||
name: WriteOperations.PushCase as const,
|
name: WriteOperations.PushCase as const,
|
||||||
action: 'case_push',
|
action: 'case_push',
|
||||||
verbs: updateVerbs,
|
verbs: pushVerbs,
|
||||||
docType: 'case',
|
docType: 'case',
|
||||||
savedObjectType: CASE_SAVED_OBJECT,
|
savedObjectType: CASE_SAVED_OBJECT,
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,6 +15,9 @@ import { createCasesRoute } from '../create_cases_route';
|
||||||
export const getConnectorsRoute = createCasesRoute({
|
export const getConnectorsRoute = createCasesRoute({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: `${CASE_CONFIGURE_CONNECTORS_URL}/_find`,
|
path: `${CASE_CONFIGURE_CONNECTORS_URL}/_find`,
|
||||||
|
routerOptions: {
|
||||||
|
tags: ['access:casesGetConnectorsConfigure'],
|
||||||
|
},
|
||||||
handler: async ({ context, response }) => {
|
handler: async ({ context, response }) => {
|
||||||
try {
|
try {
|
||||||
const caseContext = await context.cases;
|
const caseContext = await context.cases;
|
||||||
|
|
|
@ -40,7 +40,7 @@ interface CaseRouteHandlerArguments<P, Q, B> {
|
||||||
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
|
kibanaVersion: PluginInitializerContext['env']['packageInfo']['version'];
|
||||||
}
|
}
|
||||||
|
|
||||||
type CaseRouteTags = 'access:casesSuggestUserProfiles';
|
type CaseRouteTags = 'access:casesSuggestUserProfiles' | 'access:casesGetConnectorsConfigure';
|
||||||
|
|
||||||
export interface CaseRoute<P = unknown, Q = unknown, B = unknown> {
|
export interface CaseRoute<P = unknown, Q = unknown, B = unknown> {
|
||||||
method: 'get' | 'post' | 'put' | 'delete' | 'patch';
|
method: 'get' | 'post' | 'put' | 'delete' | 'patch';
|
||||||
|
|
|
@ -110,6 +110,7 @@ describe('AddToCaseAction', function () {
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,6 +18,7 @@ export function useGetUserCasesPermissions() {
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
});
|
});
|
||||||
const uiCapabilities = useKibana().services.application.capabilities;
|
const uiCapabilities = useKibana().services.application.capabilities;
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ export function useGetUserCasesPermissions() {
|
||||||
update: casesCapabilities.update,
|
update: casesCapabilities.update,
|
||||||
delete: casesCapabilities.delete,
|
delete: casesCapabilities.delete,
|
||||||
push: casesCapabilities.push,
|
push: casesCapabilities.push,
|
||||||
|
connectors: casesCapabilities.connectors,
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
casesCapabilities.all,
|
casesCapabilities.all,
|
||||||
|
@ -41,6 +43,7 @@ export function useGetUserCasesPermissions() {
|
||||||
casesCapabilities.update,
|
casesCapabilities.update,
|
||||||
casesCapabilities.delete,
|
casesCapabilities.delete,
|
||||||
casesCapabilities.push,
|
casesCapabilities.push,
|
||||||
|
casesCapabilities.connectors,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return casesPermissions;
|
return casesPermissions;
|
||||||
|
|
|
@ -19,7 +19,15 @@ export default {
|
||||||
const Template: ComponentStory<typeof Component> = (props: CasesProps) => <Component {...props} />;
|
const Template: ComponentStory<typeof Component> = (props: CasesProps) => <Component {...props} />;
|
||||||
|
|
||||||
const defaultProps: CasesProps = {
|
const defaultProps: CasesProps = {
|
||||||
permissions: { read: true, all: true, create: true, delete: true, push: true, update: true },
|
permissions: {
|
||||||
|
read: true,
|
||||||
|
all: true,
|
||||||
|
create: true,
|
||||||
|
delete: true,
|
||||||
|
push: true,
|
||||||
|
update: true,
|
||||||
|
connectors: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CasesPageWithAllPermissions = Template.bind({});
|
export const CasesPageWithAllPermissions = Template.bind({});
|
||||||
|
@ -34,5 +42,6 @@ CasesPageWithNoPermissions.args = {
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
update: false,
|
update: false,
|
||||||
|
connectors: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,6 +19,7 @@ export function useGetUserCasesPermissions() {
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
});
|
});
|
||||||
const uiCapabilities = useKibana().services.application!.capabilities;
|
const uiCapabilities = useKibana().services.application!.capabilities;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ export function useGetUserCasesPermissions() {
|
||||||
update: casesCapabilities.update,
|
update: casesCapabilities.update,
|
||||||
delete: casesCapabilities.delete,
|
delete: casesCapabilities.delete,
|
||||||
push: casesCapabilities.push,
|
push: casesCapabilities.push,
|
||||||
|
connectors: casesCapabilities.connectors,
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
casesCapabilities.all,
|
casesCapabilities.all,
|
||||||
|
@ -43,6 +45,7 @@ export function useGetUserCasesPermissions() {
|
||||||
casesCapabilities.update,
|
casesCapabilities.update,
|
||||||
casesCapabilities.delete,
|
casesCapabilities.delete,
|
||||||
casesCapabilities.push,
|
casesCapabilities.push,
|
||||||
|
casesCapabilities.connectors,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return casesPermissions;
|
return casesPermissions;
|
||||||
|
|
|
@ -12,4 +12,5 @@ export const noCasesPermissions = () => ({
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const noCasesCapabilities = () => ({
|
||||||
update_cases: false,
|
update_cases: false,
|
||||||
delete_cases: false,
|
delete_cases: false,
|
||||||
push_cases: false,
|
push_cases: false,
|
||||||
|
cases_connector: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const readCasesCapabilities = () => ({
|
export const readCasesCapabilities = () => ({
|
||||||
|
@ -19,6 +20,7 @@ export const readCasesCapabilities = () => ({
|
||||||
update_cases: false,
|
update_cases: false,
|
||||||
delete_cases: false,
|
delete_cases: false,
|
||||||
push_cases: false,
|
push_cases: false,
|
||||||
|
cases_connector: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const allCasesCapabilities = () => ({
|
export const allCasesCapabilities = () => ({
|
||||||
|
@ -27,6 +29,7 @@ export const allCasesCapabilities = () => ({
|
||||||
update_cases: true,
|
update_cases: true,
|
||||||
delete_cases: true,
|
delete_cases: true,
|
||||||
push_cases: true,
|
push_cases: true,
|
||||||
|
cases_connector: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const noCasesPermissions = () => ({
|
export const noCasesPermissions = () => ({
|
||||||
|
@ -36,6 +39,7 @@ export const noCasesPermissions = () => ({
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const readCasesPermissions = () => ({
|
export const readCasesPermissions = () => ({
|
||||||
|
@ -45,6 +49,7 @@ export const readCasesPermissions = () => ({
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const writeCasesPermissions = () => ({
|
export const writeCasesPermissions = () => ({
|
||||||
|
@ -54,6 +59,7 @@ export const writeCasesPermissions = () => ({
|
||||||
update: true,
|
update: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
push: true,
|
push: true,
|
||||||
|
connectors: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const allCasesPermissions = () => ({
|
export const allCasesPermissions = () => ({
|
||||||
|
@ -63,4 +69,5 @@ export const allCasesPermissions = () => ({
|
||||||
update: true,
|
update: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
push: true,
|
push: true,
|
||||||
|
connectors: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -161,6 +161,7 @@ export const useGetUserCasesPermissions = () => {
|
||||||
update: false,
|
update: false,
|
||||||
delete: false,
|
delete: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
connectors: false,
|
||||||
});
|
});
|
||||||
const uiCapabilities = useKibana().services.application.capabilities;
|
const uiCapabilities = useKibana().services.application.capabilities;
|
||||||
const casesCapabilities = useKibana().services.cases.helpers.getUICapabilities(
|
const casesCapabilities = useKibana().services.cases.helpers.getUICapabilities(
|
||||||
|
@ -175,6 +176,7 @@ export const useGetUserCasesPermissions = () => {
|
||||||
update: casesCapabilities.update,
|
update: casesCapabilities.update,
|
||||||
delete: casesCapabilities.delete,
|
delete: casesCapabilities.delete,
|
||||||
push: casesCapabilities.push,
|
push: casesCapabilities.push,
|
||||||
|
connectors: casesCapabilities.connectors,
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
casesCapabilities.all,
|
casesCapabilities.all,
|
||||||
|
@ -183,6 +185,7 @@ export const useGetUserCasesPermissions = () => {
|
||||||
casesCapabilities.update,
|
casesCapabilities.update,
|
||||||
casesCapabilities.delete,
|
casesCapabilities.delete,
|
||||||
casesCapabilities.push,
|
casesCapabilities.push,
|
||||||
|
casesCapabilities.connectors,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return casesPermissions;
|
return casesPermissions;
|
||||||
|
|
|
@ -13,6 +13,10 @@ import {
|
||||||
createUICapabilities as createCasesUICapabilities,
|
createUICapabilities as createCasesUICapabilities,
|
||||||
getApiTags as getCasesApiTags,
|
getApiTags as getCasesApiTags,
|
||||||
} from '@kbn/cases-plugin/common';
|
} from '@kbn/cases-plugin/common';
|
||||||
|
import {
|
||||||
|
CASES_CONNECTORS_CAPABILITY,
|
||||||
|
GET_CONNECTORS_CONFIGURE_API_TAG,
|
||||||
|
} from '@kbn/cases-plugin/common/constants';
|
||||||
import type { AppFeaturesCasesConfig, BaseKibanaFeatureConfig } from './types';
|
import type { AppFeaturesCasesConfig, BaseKibanaFeatureConfig } from './types';
|
||||||
import { APP_ID, CASES_FEATURE_ID } from '../../../common/constants';
|
import { APP_ID, CASES_FEATURE_ID } from '../../../common/constants';
|
||||||
import { CasesSubFeatureId } from './security_cases_kibana_sub_features';
|
import { CasesSubFeatureId } from './security_cases_kibana_sub_features';
|
||||||
|
@ -21,48 +25,66 @@ import { AppFeatureCasesKey } from '../../../common/types/app_features';
|
||||||
const casesCapabilities = createCasesUICapabilities();
|
const casesCapabilities = createCasesUICapabilities();
|
||||||
const casesApiTags = getCasesApiTags(APP_ID);
|
const casesApiTags = getCasesApiTags(APP_ID);
|
||||||
|
|
||||||
export const getCasesBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({
|
export const getCasesBaseKibanaFeature = (): BaseKibanaFeatureConfig => {
|
||||||
id: CASES_FEATURE_ID,
|
// On SecuritySolution essentials cases does not have the connector feature
|
||||||
name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle', {
|
const casesAllUICapabilities = casesCapabilities.all.filter(
|
||||||
defaultMessage: 'Cases',
|
(capability) => capability !== CASES_CONNECTORS_CAPABILITY
|
||||||
}),
|
);
|
||||||
order: 1100,
|
|
||||||
category: DEFAULT_APP_CATEGORIES.security,
|
const casesReadUICapabilities = casesCapabilities.read.filter(
|
||||||
app: [CASES_FEATURE_ID, 'kibana'],
|
(capability) => capability !== CASES_CONNECTORS_CAPABILITY
|
||||||
catalogue: [APP_ID],
|
);
|
||||||
cases: [APP_ID],
|
|
||||||
privileges: {
|
const casesAllAPICapabilities = casesApiTags.all.filter(
|
||||||
all: {
|
(capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG
|
||||||
api: casesApiTags.all,
|
);
|
||||||
app: [CASES_FEATURE_ID, 'kibana'],
|
|
||||||
catalogue: [APP_ID],
|
const casesReadAPICapabilities = casesApiTags.read.filter(
|
||||||
cases: {
|
(capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG
|
||||||
create: [APP_ID],
|
);
|
||||||
read: [APP_ID],
|
|
||||||
update: [APP_ID],
|
return {
|
||||||
push: [APP_ID],
|
id: CASES_FEATURE_ID,
|
||||||
|
name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle', {
|
||||||
|
defaultMessage: 'Cases',
|
||||||
|
}),
|
||||||
|
order: 1100,
|
||||||
|
category: DEFAULT_APP_CATEGORIES.security,
|
||||||
|
app: [CASES_FEATURE_ID, 'kibana'],
|
||||||
|
catalogue: [APP_ID],
|
||||||
|
cases: [APP_ID],
|
||||||
|
privileges: {
|
||||||
|
all: {
|
||||||
|
api: casesAllAPICapabilities,
|
||||||
|
app: [CASES_FEATURE_ID, 'kibana'],
|
||||||
|
catalogue: [APP_ID],
|
||||||
|
cases: {
|
||||||
|
create: [APP_ID],
|
||||||
|
read: [APP_ID],
|
||||||
|
update: [APP_ID],
|
||||||
|
},
|
||||||
|
savedObject: {
|
||||||
|
all: [...filesSavedObjectTypes],
|
||||||
|
read: [...filesSavedObjectTypes],
|
||||||
|
},
|
||||||
|
ui: casesAllUICapabilities,
|
||||||
},
|
},
|
||||||
savedObject: {
|
read: {
|
||||||
all: [...filesSavedObjectTypes],
|
api: casesReadAPICapabilities,
|
||||||
read: [...filesSavedObjectTypes],
|
app: [CASES_FEATURE_ID, 'kibana'],
|
||||||
|
catalogue: [APP_ID],
|
||||||
|
cases: {
|
||||||
|
read: [APP_ID],
|
||||||
|
},
|
||||||
|
savedObject: {
|
||||||
|
all: [],
|
||||||
|
read: [...filesSavedObjectTypes],
|
||||||
|
},
|
||||||
|
ui: casesReadUICapabilities,
|
||||||
},
|
},
|
||||||
ui: casesCapabilities.all,
|
|
||||||
},
|
},
|
||||||
read: {
|
};
|
||||||
api: casesApiTags.read,
|
};
|
||||||
app: [CASES_FEATURE_ID, 'kibana'],
|
|
||||||
catalogue: [APP_ID],
|
|
||||||
cases: {
|
|
||||||
read: [APP_ID],
|
|
||||||
},
|
|
||||||
savedObject: {
|
|
||||||
all: [],
|
|
||||||
read: [...filesSavedObjectTypes],
|
|
||||||
},
|
|
||||||
ui: casesCapabilities.read,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getCasesBaseKibanaSubFeatureIds = (): CasesSubFeatureId[] => [
|
export const getCasesBaseKibanaSubFeatureIds = (): CasesSubFeatureId[] => [
|
||||||
CasesSubFeatureId.deleteCases,
|
CasesSubFeatureId.deleteCases,
|
||||||
|
@ -79,6 +101,18 @@ export const getCasesBaseKibanaSubFeatureIds = (): CasesSubFeatureId[] => [
|
||||||
*/
|
*/
|
||||||
export const getCasesAppFeaturesConfig = (): AppFeaturesCasesConfig => ({
|
export const getCasesAppFeaturesConfig = (): AppFeaturesCasesConfig => ({
|
||||||
[AppFeatureCasesKey.casesConnectors]: {
|
[AppFeatureCasesKey.casesConnectors]: {
|
||||||
// TODO: Add cases connector configuration privileges
|
privileges: {
|
||||||
|
all: {
|
||||||
|
api: [GET_CONNECTORS_CONFIGURE_API_TAG], // Add cases connector get connectors API privileges
|
||||||
|
ui: [CASES_CONNECTORS_CAPABILITY], // Add cases connector UI privileges
|
||||||
|
cases: {
|
||||||
|
push: [APP_ID], // Add cases connector push privileges
|
||||||
|
},
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
api: [GET_CONNECTORS_CONFIGURE_API_TAG], // Add cases connector get connectors API privileges
|
||||||
|
ui: [CASES_CONNECTORS_CAPABILITY], // Add cases connector UI privileges
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,6 +44,30 @@ export const noCasesPrivilegesSpace1: Role = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const noCasesConnectors: Role = {
|
||||||
|
name: 'no_cases_connectors',
|
||||||
|
privileges: {
|
||||||
|
elasticsearch: {
|
||||||
|
indices: [
|
||||||
|
{
|
||||||
|
names: ['*'],
|
||||||
|
privileges: ['all'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
kibana: [
|
||||||
|
{
|
||||||
|
feature: {
|
||||||
|
testNoCasesConnectorFixture: ['all'],
|
||||||
|
actions: ['all'],
|
||||||
|
actionsSimulators: ['all'],
|
||||||
|
},
|
||||||
|
spaces: ['*'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const globalRead: Role = {
|
export const globalRead: Role = {
|
||||||
name: 'global_read',
|
name: 'global_read',
|
||||||
privileges: {
|
privileges: {
|
||||||
|
@ -353,6 +377,7 @@ export const securitySolutionOnlyAllSpacesRole: Role = {
|
||||||
export const roles = [
|
export const roles = [
|
||||||
noKibanaPrivileges,
|
noKibanaPrivileges,
|
||||||
noCasesPrivilegesSpace1,
|
noCasesPrivilegesSpace1,
|
||||||
|
noCasesConnectors,
|
||||||
globalRead,
|
globalRead,
|
||||||
securitySolutionOnlyAll,
|
securitySolutionOnlyAll,
|
||||||
securitySolutionOnlyRead,
|
securitySolutionOnlyRead,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
securitySolutionOnlyReadAlerts,
|
securitySolutionOnlyReadAlerts,
|
||||||
securitySolutionOnlyReadNoIndexAlerts,
|
securitySolutionOnlyReadNoIndexAlerts,
|
||||||
securitySolutionOnlyReadDelete,
|
securitySolutionOnlyReadDelete,
|
||||||
|
noCasesConnectors as noCasesConnectorRole,
|
||||||
} from './roles';
|
} from './roles';
|
||||||
import { User } from './types';
|
import { User } from './types';
|
||||||
|
|
||||||
|
@ -126,6 +127,12 @@ export const noCasesPrivilegesSpace1: User = {
|
||||||
roles: [noCasesPrivilegesSpace1Role.name],
|
roles: [noCasesPrivilegesSpace1Role.name],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const noCasesConnectors: User = {
|
||||||
|
username: 'no_cases_connectors',
|
||||||
|
password: 'no_cases_connectors',
|
||||||
|
roles: [noCasesConnectorRole.name],
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These users will have access to all spaces.
|
* These users will have access to all spaces.
|
||||||
*/
|
*/
|
||||||
|
@ -154,4 +161,5 @@ export const users = [
|
||||||
noKibanaPrivileges,
|
noKibanaPrivileges,
|
||||||
noCasesPrivilegesSpace1,
|
noCasesPrivilegesSpace1,
|
||||||
testDisabled,
|
testDisabled,
|
||||||
|
noCasesConnectors,
|
||||||
];
|
];
|
||||||
|
|
|
@ -40,6 +40,45 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
||||||
|
|
||||||
registerRoutes(core, this.log);
|
registerRoutes(core, this.log);
|
||||||
registerCaseFixtureFileKinds(deps.files);
|
registerCaseFixtureFileKinds(deps.files);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kibana features
|
||||||
|
*/
|
||||||
|
|
||||||
|
deps.features.registerKibanaFeature({
|
||||||
|
id: 'testNoCasesConnectorFixture',
|
||||||
|
name: 'TestNoCasesConnectorFixture',
|
||||||
|
app: ['kibana'],
|
||||||
|
category: { id: 'cases-fixtures', label: 'Cases Fixtures' },
|
||||||
|
cases: ['testNoCasesConnectorFixture'],
|
||||||
|
privileges: {
|
||||||
|
all: {
|
||||||
|
api: [],
|
||||||
|
app: ['kibana'],
|
||||||
|
cases: {
|
||||||
|
create: ['testNoCasesConnectorFixture'],
|
||||||
|
read: ['testNoCasesConnectorFixture'],
|
||||||
|
update: ['testNoCasesConnectorFixture'],
|
||||||
|
},
|
||||||
|
savedObject: {
|
||||||
|
all: [],
|
||||||
|
read: [],
|
||||||
|
},
|
||||||
|
ui: [],
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
app: ['kibana'],
|
||||||
|
cases: {
|
||||||
|
read: ['testNoCasesConnectorFixture'],
|
||||||
|
},
|
||||||
|
savedObject: {
|
||||||
|
all: [],
|
||||||
|
read: [],
|
||||||
|
},
|
||||||
|
ui: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(core: CoreStart, plugins: FixtureStartDeps) {}
|
public start(core: CoreStart, plugins: FixtureStartDeps) {}
|
||||||
|
|
|
@ -31,7 +31,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
||||||
cases: ['observabilityFixture'],
|
cases: ['observabilityFixture'],
|
||||||
privileges: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'],
|
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles', 'casesGetConnectorsConfigure'],
|
||||||
app: ['kibana'],
|
app: ['kibana'],
|
||||||
cases: {
|
cases: {
|
||||||
all: ['observabilityFixture'],
|
all: ['observabilityFixture'],
|
||||||
|
@ -43,7 +43,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
||||||
ui: [],
|
ui: [],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'],
|
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles', 'casesGetConnectorsConfigure'],
|
||||||
app: ['kibana'],
|
app: ['kibana'],
|
||||||
cases: {
|
cases: {
|
||||||
read: ['observabilityFixture'],
|
read: ['observabilityFixture'],
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
||||||
cases: ['securitySolutionFixture'],
|
cases: ['securitySolutionFixture'],
|
||||||
privileges: {
|
privileges: {
|
||||||
all: {
|
all: {
|
||||||
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'],
|
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles', 'casesGetConnectorsConfigure'],
|
||||||
app: ['kibana'],
|
app: ['kibana'],
|
||||||
cases: {
|
cases: {
|
||||||
create: ['securitySolutionFixture'],
|
create: ['securitySolutionFixture'],
|
||||||
|
@ -54,7 +54,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
|
||||||
ui: [],
|
ui: [],
|
||||||
},
|
},
|
||||||
read: {
|
read: {
|
||||||
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles'],
|
api: ['casesSuggestUserProfiles', 'bulkGetUserProfiles', 'casesGetConnectorsConfigure'],
|
||||||
app: ['kibana'],
|
app: ['kibana'],
|
||||||
cases: {
|
cases: {
|
||||||
read: ['securitySolutionFixture'],
|
read: ['securitySolutionFixture'],
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain';
|
import { ConnectorTypes } from '@kbn/cases-plugin/common/types/domain';
|
||||||
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
|
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||||
|
|
||||||
import { postCaseReq } from '../../../../common/lib/mock';
|
import { postCaseReq } from '../../../../common/lib/mock';
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -54,6 +54,7 @@ import {
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
import {
|
import {
|
||||||
globalRead,
|
globalRead,
|
||||||
|
noCasesConnectors,
|
||||||
noKibanaPrivileges,
|
noKibanaPrivileges,
|
||||||
obsOnlyRead,
|
obsOnlyRead,
|
||||||
obsSecRead,
|
obsSecRead,
|
||||||
|
@ -840,6 +841,24 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
|
|
||||||
expect(theCase.status).to.eql('open');
|
expect(theCase.status).to.eql('open');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return 403 when the user does not have access to push', async () => {
|
||||||
|
const { postedCase } = await createCaseWithConnector({
|
||||||
|
supertest,
|
||||||
|
serviceNowSimulatorURL,
|
||||||
|
actionsRemover,
|
||||||
|
configureReq: { owner: 'testNoCasesConnectorFixture' },
|
||||||
|
createCaseReq: { ...getPostCaseRequest(), owner: 'testNoCasesConnectorFixture' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await pushCase({
|
||||||
|
supertest: supertestWithoutAuth,
|
||||||
|
caseId: postedCase.id,
|
||||||
|
connectorId: postedCase.connector.id,
|
||||||
|
expectedHttpCode: 403,
|
||||||
|
auth: { user: noCasesConnectors, space: null },
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import expect from '@kbn/expect';
|
import expect from '@kbn/expect';
|
||||||
import { FtrProviderContext } from '../../../../../common/ftr_provider_context';
|
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||||
|
|
||||||
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
|
import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib';
|
||||||
import {
|
import {
|
||||||
|
@ -20,10 +20,12 @@ import {
|
||||||
getCaseConnectors,
|
getCaseConnectors,
|
||||||
getCasesWebhookConnector,
|
getCasesWebhookConnector,
|
||||||
} from '../../../../common/lib/api';
|
} from '../../../../common/lib/api';
|
||||||
|
import { noCasesConnectors } from '../../../../common/lib/authentication/users';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default ({ getService }: FtrProviderContext): void => {
|
export default ({ getService }: FtrProviderContext): void => {
|
||||||
const supertest = getService('supertest');
|
const supertest = getService('supertest');
|
||||||
|
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||||
const actionsRemover = new ActionsRemover(supertest);
|
const actionsRemover = new ActionsRemover(supertest);
|
||||||
|
|
||||||
describe('get_connectors', () => {
|
describe('get_connectors', () => {
|
||||||
|
@ -184,5 +186,13 @@ export default ({ getService }: FtrProviderContext): void => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return 403 when the user does not have access to the case connectors', async () => {
|
||||||
|
await getCaseConnectors({
|
||||||
|
supertest: supertestWithoutAuth,
|
||||||
|
auth: { user: noCasesConnectors, space: null },
|
||||||
|
expectedHttpCode: 403,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -42,6 +42,7 @@ const permissions = {
|
||||||
update: true,
|
update: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
push: true,
|
push: true,
|
||||||
|
connectors: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachments = [{ type: AttachmentType.user as const, comment: 'test' }];
|
const attachments = [{ type: AttachmentType.user as const, comment: 'test' }];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue