mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[SecuritySolution] Breaking out timeline & note privileges (#201780)](https://github.com/elastic/kibana/pull/201780) <!--- Backport version: 9.6.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Jan Monschke","email":"jan.monschke@elastic.co"},"sourceCommit":{"committedDate":"2025-01-20T13:09:16Z","message":"[SecuritySolution] Breaking out timeline & note privileges (#201780)\n\n## Summary\n\nEpic: https://github.com/elastic/security-team/issues/7998\n\nIn this PR we're breaking out the `timeline` and `notes` features into\ntheir own feature privilege definition. Previously, access to both\nfeatures was granted implicitly through the `siem` feature. However, we\nfound that this level of access control is not sufficient for all\nclients who wanted a more fine-grained way to grant access to parts of\nsecurity solution.\n\nIn order to break out `timeline` and `notes` from `siem`, we had to\ndeprecate it feature privilege definition for. That is why you'll find\nplenty of changes of `siem` to `siemV2` in this PR. We're making use of\nthe feature privilege's `replacedBy` functionality, allowing for a\nseamless migration of deprecated roles.\n\nThis means that roles that previously granted `siem.all` are now granted\n`siemV2.all`, `timeline.all` and `notes.all` (same for `*.read`).\nExisting users are not impacted and should all still have the correct\naccess. We added tests to make sure this is working as expected.\n\nAlongside the `ui` privileges, this PR also adds dedicated API tags.\nThose tags haven been added to the new and previous version of the\nprivilege definitions to allow for a clean migration:\n\n```mermaid\nflowchart LR\n subgraph v1\n A(siem) --> Y(all)\n A --> X(read)\n Y -->|api| W(timeline_write / timeline_read / notes_read / notes_write)\n X -->|api| V(timeline_read /notes_read)\n end\n\n subgraph v2\n A-->|replacedBy| C[siemV2]\n A-->|replacedBy| E[timeline]\n A-->|replacedBy| G[notes]\n \n\n E --> L(all)\n E --> M(read)\n L -->|api| N(timeline_write / timeline_read)\n M -->|api| P(timeline_read)\n\n G --> Q(all)\n G --> I(read)\n\n Q -->|api| R(notes_write / notes_read)\n I -->|api| S(notes_read)\n end\n```\n\n### Visual changes\n\n#### Hidden/disabled elements\n\nMost of the changes are happening \"under\" the hood and are only\nexpressed in case a user has a role with `timeline.none` or\n`notes.none`. This would hide and/or disable elements that would usually\nallow them to interact with either timeline or the notes feature (within\ntimeline or the event flyout currently).\n\nAs an example, this is how the hover actions look for a user with and\nwithout timeline access:\n\n| With timeline access | Without timeline access |\n| --- | --- |\n| <img width=\"616\" alt=\"Screenshot 2024-12-18 at 17 22 49\"\nsrc=\"https://github.com/user-attachments/assets/a767fbb5-49c8-422a-817e-23e7fe1f0042\"\n/> | <img width=\"724\" alt=\"Screenshot 2024-12-18 at 17 23 29\"\nsrc=\"https://github.com/user-attachments/assets/3490306a-d1c3-41aa-af5b-05a1dd804b47\"\n/> |\n\n#### Roles\n\nAnother visible change of this PR is the addition of `Timeline` and\n`Notes` in the edit-role screen:\n\n| Before | After |\n| ------- | ------ |\n| <img width=\"746\" alt=\"Screenshot 2024-12-12 at 16 31 43\"\nsrc=\"https://github.com/user-attachments/assets/20a80dd4-c214-48a5-8c6e-3dc19c0cbc43\"\n/> | <img width=\"738\" alt=\"Screenshot 2024-12-12 at 16 32 53\"\nsrc=\"https://github.com/user-attachments/assets/afb1eab4-1729-4c4e-9f51-fddabc32b1dd\"\n/> |\n\nWe made sure that for migrated roles that hard `security.all` selected,\nthis screen correctly shows `security.all`, `timeline.all` and\n`notes.all` after the privilege migration.\n\n#### Timeline toast\n\nThere are tons of places in security solution where `Investigate / Add\nto timeline` are shown. We did our best to disable all of these actions\nbut there is no guarantee that this PR catches all the places where we\nlink to timeline (actions). One layer of extra protection is that the\nAPI endpoints don't give access to timelines to users without the\ncorrect privileges. Another one is a Redux middleware that makes sure\ntimelines cannot be shown in missed cases. The following toast will be\nshown instead of the timeline:\n\n<img width=\"354\" alt=\"Screenshot 2024-12-19 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/1304005e-2753-4268-b6e7-bd7e22d8a1e3\"\n/>\n\n### Changes to predefined security roles\n\nAll predefined security roles have been updated to grant the new\nprivileges (in ESS and serverless). In accordance with the migration,\nall roles with `siem.all` have been assigned `siemV2.all`,\n`timeline.all` and `notes.all` (and `*.read` respectively).\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: PhilippeOberti <philippe.oberti@elastic.co>\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"1b167d9dc23a9e0e8e47992a37563ca89ccf3c7d","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Fleet","v9.0.0","release_note:feature","Team:Threat Hunting:Investigations","backport:prev-minor","ci:cloud-deploy","ci:project-persist-deployment","v8.18.0"],"title":"[SecuritySolution] Breaking out timeline & note privileges","number":201780,"url":"https://github.com/elastic/kibana/pull/201780","mergeCommit":{"message":"[SecuritySolution] Breaking out timeline & note privileges (#201780)\n\n## Summary\n\nEpic: https://github.com/elastic/security-team/issues/7998\n\nIn this PR we're breaking out the `timeline` and `notes` features into\ntheir own feature privilege definition. Previously, access to both\nfeatures was granted implicitly through the `siem` feature. However, we\nfound that this level of access control is not sufficient for all\nclients who wanted a more fine-grained way to grant access to parts of\nsecurity solution.\n\nIn order to break out `timeline` and `notes` from `siem`, we had to\ndeprecate it feature privilege definition for. That is why you'll find\nplenty of changes of `siem` to `siemV2` in this PR. We're making use of\nthe feature privilege's `replacedBy` functionality, allowing for a\nseamless migration of deprecated roles.\n\nThis means that roles that previously granted `siem.all` are now granted\n`siemV2.all`, `timeline.all` and `notes.all` (same for `*.read`).\nExisting users are not impacted and should all still have the correct\naccess. We added tests to make sure this is working as expected.\n\nAlongside the `ui` privileges, this PR also adds dedicated API tags.\nThose tags haven been added to the new and previous version of the\nprivilege definitions to allow for a clean migration:\n\n```mermaid\nflowchart LR\n subgraph v1\n A(siem) --> Y(all)\n A --> X(read)\n Y -->|api| W(timeline_write / timeline_read / notes_read / notes_write)\n X -->|api| V(timeline_read /notes_read)\n end\n\n subgraph v2\n A-->|replacedBy| C[siemV2]\n A-->|replacedBy| E[timeline]\n A-->|replacedBy| G[notes]\n \n\n E --> L(all)\n E --> M(read)\n L -->|api| N(timeline_write / timeline_read)\n M -->|api| P(timeline_read)\n\n G --> Q(all)\n G --> I(read)\n\n Q -->|api| R(notes_write / notes_read)\n I -->|api| S(notes_read)\n end\n```\n\n### Visual changes\n\n#### Hidden/disabled elements\n\nMost of the changes are happening \"under\" the hood and are only\nexpressed in case a user has a role with `timeline.none` or\n`notes.none`. This would hide and/or disable elements that would usually\nallow them to interact with either timeline or the notes feature (within\ntimeline or the event flyout currently).\n\nAs an example, this is how the hover actions look for a user with and\nwithout timeline access:\n\n| With timeline access | Without timeline access |\n| --- | --- |\n| <img width=\"616\" alt=\"Screenshot 2024-12-18 at 17 22 49\"\nsrc=\"https://github.com/user-attachments/assets/a767fbb5-49c8-422a-817e-23e7fe1f0042\"\n/> | <img width=\"724\" alt=\"Screenshot 2024-12-18 at 17 23 29\"\nsrc=\"https://github.com/user-attachments/assets/3490306a-d1c3-41aa-af5b-05a1dd804b47\"\n/> |\n\n#### Roles\n\nAnother visible change of this PR is the addition of `Timeline` and\n`Notes` in the edit-role screen:\n\n| Before | After |\n| ------- | ------ |\n| <img width=\"746\" alt=\"Screenshot 2024-12-12 at 16 31 43\"\nsrc=\"https://github.com/user-attachments/assets/20a80dd4-c214-48a5-8c6e-3dc19c0cbc43\"\n/> | <img width=\"738\" alt=\"Screenshot 2024-12-12 at 16 32 53\"\nsrc=\"https://github.com/user-attachments/assets/afb1eab4-1729-4c4e-9f51-fddabc32b1dd\"\n/> |\n\nWe made sure that for migrated roles that hard `security.all` selected,\nthis screen correctly shows `security.all`, `timeline.all` and\n`notes.all` after the privilege migration.\n\n#### Timeline toast\n\nThere are tons of places in security solution where `Investigate / Add\nto timeline` are shown. We did our best to disable all of these actions\nbut there is no guarantee that this PR catches all the places where we\nlink to timeline (actions). One layer of extra protection is that the\nAPI endpoints don't give access to timelines to users without the\ncorrect privileges. Another one is a Redux middleware that makes sure\ntimelines cannot be shown in missed cases. The following toast will be\nshown instead of the timeline:\n\n<img width=\"354\" alt=\"Screenshot 2024-12-19 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/1304005e-2753-4268-b6e7-bd7e22d8a1e3\"\n/>\n\n### Changes to predefined security roles\n\nAll predefined security roles have been updated to grant the new\nprivileges (in ESS and serverless). In accordance with the migration,\nall roles with `siem.all` have been assigned `siemV2.all`,\n`timeline.all` and `notes.all` (and `*.read` respectively).\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: PhilippeOberti <philippe.oberti@elastic.co>\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"1b167d9dc23a9e0e8e47992a37563ca89ccf3c7d"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201780","number":201780,"mergeCommit":{"message":"[SecuritySolution] Breaking out timeline & note privileges (#201780)\n\n## Summary\n\nEpic: https://github.com/elastic/security-team/issues/7998\n\nIn this PR we're breaking out the `timeline` and `notes` features into\ntheir own feature privilege definition. Previously, access to both\nfeatures was granted implicitly through the `siem` feature. However, we\nfound that this level of access control is not sufficient for all\nclients who wanted a more fine-grained way to grant access to parts of\nsecurity solution.\n\nIn order to break out `timeline` and `notes` from `siem`, we had to\ndeprecate it feature privilege definition for. That is why you'll find\nplenty of changes of `siem` to `siemV2` in this PR. We're making use of\nthe feature privilege's `replacedBy` functionality, allowing for a\nseamless migration of deprecated roles.\n\nThis means that roles that previously granted `siem.all` are now granted\n`siemV2.all`, `timeline.all` and `notes.all` (same for `*.read`).\nExisting users are not impacted and should all still have the correct\naccess. We added tests to make sure this is working as expected.\n\nAlongside the `ui` privileges, this PR also adds dedicated API tags.\nThose tags haven been added to the new and previous version of the\nprivilege definitions to allow for a clean migration:\n\n```mermaid\nflowchart LR\n subgraph v1\n A(siem) --> Y(all)\n A --> X(read)\n Y -->|api| W(timeline_write / timeline_read / notes_read / notes_write)\n X -->|api| V(timeline_read /notes_read)\n end\n\n subgraph v2\n A-->|replacedBy| C[siemV2]\n A-->|replacedBy| E[timeline]\n A-->|replacedBy| G[notes]\n \n\n E --> L(all)\n E --> M(read)\n L -->|api| N(timeline_write / timeline_read)\n M -->|api| P(timeline_read)\n\n G --> Q(all)\n G --> I(read)\n\n Q -->|api| R(notes_write / notes_read)\n I -->|api| S(notes_read)\n end\n```\n\n### Visual changes\n\n#### Hidden/disabled elements\n\nMost of the changes are happening \"under\" the hood and are only\nexpressed in case a user has a role with `timeline.none` or\n`notes.none`. This would hide and/or disable elements that would usually\nallow them to interact with either timeline or the notes feature (within\ntimeline or the event flyout currently).\n\nAs an example, this is how the hover actions look for a user with and\nwithout timeline access:\n\n| With timeline access | Without timeline access |\n| --- | --- |\n| <img width=\"616\" alt=\"Screenshot 2024-12-18 at 17 22 49\"\nsrc=\"https://github.com/user-attachments/assets/a767fbb5-49c8-422a-817e-23e7fe1f0042\"\n/> | <img width=\"724\" alt=\"Screenshot 2024-12-18 at 17 23 29\"\nsrc=\"https://github.com/user-attachments/assets/3490306a-d1c3-41aa-af5b-05a1dd804b47\"\n/> |\n\n#### Roles\n\nAnother visible change of this PR is the addition of `Timeline` and\n`Notes` in the edit-role screen:\n\n| Before | After |\n| ------- | ------ |\n| <img width=\"746\" alt=\"Screenshot 2024-12-12 at 16 31 43\"\nsrc=\"https://github.com/user-attachments/assets/20a80dd4-c214-48a5-8c6e-3dc19c0cbc43\"\n/> | <img width=\"738\" alt=\"Screenshot 2024-12-12 at 16 32 53\"\nsrc=\"https://github.com/user-attachments/assets/afb1eab4-1729-4c4e-9f51-fddabc32b1dd\"\n/> |\n\nWe made sure that for migrated roles that hard `security.all` selected,\nthis screen correctly shows `security.all`, `timeline.all` and\n`notes.all` after the privilege migration.\n\n#### Timeline toast\n\nThere are tons of places in security solution where `Investigate / Add\nto timeline` are shown. We did our best to disable all of these actions\nbut there is no guarantee that this PR catches all the places where we\nlink to timeline (actions). One layer of extra protection is that the\nAPI endpoints don't give access to timelines to users without the\ncorrect privileges. Another one is a Redux middleware that makes sure\ntimelines cannot be shown in missed cases. The following toast will be\nshown instead of the timeline:\n\n<img width=\"354\" alt=\"Screenshot 2024-12-19 at 10 34 23\"\nsrc=\"https://github.com/user-attachments/assets/1304005e-2753-4268-b6e7-bd7e22d8a1e3\"\n/>\n\n### Changes to predefined security roles\n\nAll predefined security roles have been updated to grant the new\nprivileges (in ESS and serverless). In accordance with the migration,\nall roles with `siem.all` have been assigned `siemV2.all`,\n`timeline.all` and `notes.all` (and `*.read` respectively).\n\n### Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- [x] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- [x] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: PhilippeOberti <philippe.oberti@elastic.co>\nCo-authored-by: Steph Milovic <stephanie.milovic@elastic.co>","sha":"1b167d9dc23a9e0e8e47992a37563ca89ccf3c7d"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
f9bed85a13
commit
8e02172e2e
263 changed files with 5429 additions and 1209 deletions
|
@ -22,7 +22,7 @@ xpack.features.overrides:
|
|||
category: "security"
|
||||
order: 1101
|
||||
### Security's feature privileges are fine-tuned to grant access to Discover, Dashboard, Maps, and Visualize apps.
|
||||
siem:
|
||||
siemV2:
|
||||
privileges:
|
||||
### Security's `All` feature privilege should implicitly grant `All` access to Discover, Dashboard, Maps, and
|
||||
### Visualize features.
|
||||
|
|
|
@ -43,12 +43,14 @@ viewer:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.read
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.endpoint_list_read
|
||||
- feature_siemV2.read
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.endpoint_list_read
|
||||
- feature_securitySolutionCasesV2.read
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.read
|
||||
- feature_securitySolutionNotes.read
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.read
|
||||
- feature_osquery.read
|
||||
|
@ -113,22 +115,24 @@ editor:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all
|
||||
- feature_siem.policy_management_read # Elastic Defend Policy Management
|
||||
- feature_siem.host_isolation_all
|
||||
- feature_siem.process_operations_all
|
||||
- feature_siem.actions_log_management_all # Response actions history
|
||||
- feature_siem.file_operations_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all
|
||||
- feature_siemV2.policy_management_read # Elastic Defend Policy Management
|
||||
- feature_siemV2.host_isolation_all
|
||||
- feature_siemV2.process_operations_all
|
||||
- feature_siemV2.actions_log_management_all # Response actions history
|
||||
- feature_siemV2.file_operations_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
@ -172,12 +176,14 @@ t1_analyst:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.read
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.endpoint_list_read
|
||||
- feature_siemV2.read
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.endpoint_list_read
|
||||
- feature_securitySolutionCasesV2.read
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.read
|
||||
- feature_securitySolutionNotes.read
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.read
|
||||
- feature_osquery.read
|
||||
|
@ -227,12 +233,14 @@ t2_analyst:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.read
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.endpoint_list_read
|
||||
- feature_siemV2.read
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.endpoint_list_read
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.read
|
||||
- feature_securitySolutionNotes.read
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.read
|
||||
- feature_osquery.read
|
||||
|
@ -286,24 +294,26 @@ t3_analyst:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all
|
||||
- feature_siem.policy_management_read # Elastic Defend Policy Management
|
||||
- feature_siem.host_isolation_all
|
||||
- feature_siem.process_operations_all
|
||||
- feature_siem.actions_log_management_all # Response actions history
|
||||
- feature_siem.file_operations_all
|
||||
- feature_siem.scan_operations_all
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all
|
||||
- feature_siemV2.policy_management_read # Elastic Defend Policy Management
|
||||
- feature_siemV2.host_isolation_all
|
||||
- feature_siemV2.process_operations_all
|
||||
- feature_siemV2.actions_log_management_all # Response actions history
|
||||
- feature_siemV2.file_operations_all
|
||||
- feature_siemV2.scan_operations_all
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
@ -360,12 +370,14 @@ threat_intelligence_analyst:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.endpoint_list_read
|
||||
- feature_siem.blocklist_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.endpoint_list_read
|
||||
- feature_siemV2.blocklist_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.read
|
||||
- feature_osquery.all
|
||||
|
@ -421,20 +433,22 @@ rule_author:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.policy_management_all
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_read
|
||||
- feature_siem.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siem.actions_log_management_read
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.policy_management_all
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_read
|
||||
- feature_siemV2.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siemV2.actions_log_management_read
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.read
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
@ -489,25 +503,27 @@ soc_manager:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.policy_management_all
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all
|
||||
- feature_siem.host_isolation_all
|
||||
- feature_siem.process_operations_all
|
||||
- feature_siem.actions_log_management_all
|
||||
- feature_siem.file_operations_all
|
||||
- feature_siem.execute_operations_all
|
||||
- feature_siem.scan_operations_all
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.policy_management_all
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all
|
||||
- feature_siemV2.host_isolation_all
|
||||
- feature_siemV2.process_operations_all
|
||||
- feature_siemV2.actions_log_management_all
|
||||
- feature_siemV2.file_operations_all
|
||||
- feature_siemV2.execute_operations_all
|
||||
- feature_siemV2.scan_operations_all
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.all
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
@ -562,12 +578,14 @@ detections_admin:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.all
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.all
|
||||
- feature_builtInAlerts.all
|
||||
- feature_dev_tools.all
|
||||
|
@ -614,20 +632,22 @@ platform_engineer:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.all
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.policy_management_all
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siem.actions_log_management_read
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.policy_management_all
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siemV2.actions_log_management_read
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.all
|
||||
- feature_builtInAlerts.all
|
||||
- feature_fleet.all
|
||||
|
@ -684,24 +704,26 @@ endpoint_operations_analyst:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.read
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.policy_management_all
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all
|
||||
- feature_siem.host_isolation_all
|
||||
- feature_siem.process_operations_all
|
||||
- feature_siem.actions_log_management_all
|
||||
- feature_siem.file_operations_all
|
||||
- feature_siem.execute_operations_all
|
||||
- feature_siem.scan_operations_all
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.policy_management_all
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all
|
||||
- feature_siemV2.host_isolation_all
|
||||
- feature_siemV2.process_operations_all
|
||||
- feature_siemV2.actions_log_management_all
|
||||
- feature_siemV2.file_operations_all
|
||||
- feature_siemV2.execute_operations_all
|
||||
- feature_siemV2.scan_operations_all
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.all
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
@ -765,19 +787,21 @@ endpoint_policy_manager:
|
|||
- application: 'kibana-.kibana'
|
||||
privileges:
|
||||
- feature_ml.all
|
||||
- feature_siem.all
|
||||
- feature_siem.read_alerts
|
||||
- feature_siem.crud_alerts
|
||||
- feature_siem.policy_management_all
|
||||
- feature_siem.endpoint_list_all
|
||||
- feature_siem.trusted_applications_all
|
||||
- feature_siem.event_filters_all
|
||||
- feature_siem.host_isolation_exceptions_all
|
||||
- feature_siem.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siem.workflow_insights_all
|
||||
- feature_siemV2.all
|
||||
- feature_siemV2.read_alerts
|
||||
- feature_siemV2.crud_alerts
|
||||
- feature_siemV2.policy_management_all
|
||||
- feature_siemV2.endpoint_list_all
|
||||
- feature_siemV2.trusted_applications_all
|
||||
- feature_siemV2.event_filters_all
|
||||
- feature_siemV2.host_isolation_exceptions_all
|
||||
- feature_siemV2.blocklist_all # Elastic Defend Policy Management
|
||||
- feature_siemV2.workflow_insights_all
|
||||
- feature_securitySolutionCasesV2.all
|
||||
- feature_securitySolutionAssistant.all
|
||||
- feature_securitySolutionAttackDiscovery.all
|
||||
- feature_securitySolutionTimeline.all
|
||||
- feature_securitySolutionNotes.all
|
||||
- feature_actions.all
|
||||
- feature_builtInAlerts.all
|
||||
- feature_osquery.all
|
||||
|
|
|
@ -32,10 +32,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["read", "read_alerts"],
|
||||
"siemV2": ["read", "read_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["read"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["read"]
|
||||
},
|
||||
|
@ -79,10 +81,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["read", "read_alerts"],
|
||||
"siemV2": ["read", "read_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["read"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["read"]
|
||||
},
|
||||
|
@ -135,7 +139,7 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": [
|
||||
"siemV2": [
|
||||
"all",
|
||||
"read_alerts",
|
||||
"crud_alerts",
|
||||
|
@ -153,6 +157,8 @@
|
|||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["all"],
|
||||
"osquery": ["all"],
|
||||
|
@ -207,10 +213,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
|
@ -260,10 +268,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
|
@ -308,10 +318,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["all"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["all"],
|
||||
"dev_tools": ["all"]
|
||||
|
@ -363,10 +375,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["all"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
|
|
|
@ -42182,7 +42182,6 @@
|
|||
"xpack.securitySolution.system.withResultDescription": "avec le résultat",
|
||||
"xpack.securitySolution.tables.rowItemHelper.moreDescription": "plus non affiché",
|
||||
"xpack.securitySolution.tables.rowItemHelper.overflowButtonDescription": "+ {count} de plus",
|
||||
"xpack.securitySolution.threatIntelligence.investigateInTimelineTitle": "Investiguer dans la chronologie",
|
||||
"xpack.securitySolution.threatMatch.andDescription": "AND",
|
||||
"xpack.securitySolution.threatMatch.fieldDescription": "Champ",
|
||||
"xpack.securitySolution.threatMatch.fieldPlaceholderDescription": "Rechercher",
|
||||
|
|
|
@ -42039,7 +42039,6 @@
|
|||
"xpack.securitySolution.system.withResultDescription": "結果付き",
|
||||
"xpack.securitySolution.tables.rowItemHelper.moreDescription": "行は表示されていません",
|
||||
"xpack.securitySolution.tables.rowItemHelper.overflowButtonDescription": "他{count}件",
|
||||
"xpack.securitySolution.threatIntelligence.investigateInTimelineTitle": "タイムラインで調査",
|
||||
"xpack.securitySolution.threatMatch.andDescription": "AND",
|
||||
"xpack.securitySolution.threatMatch.fieldDescription": "フィールド",
|
||||
"xpack.securitySolution.threatMatch.fieldPlaceholderDescription": "検索",
|
||||
|
|
|
@ -42135,7 +42135,6 @@
|
|||
"xpack.securitySolution.system.withResultDescription": ",结果为",
|
||||
"xpack.securitySolution.tables.rowItemHelper.moreDescription": "未显示",
|
||||
"xpack.securitySolution.tables.rowItemHelper.overflowButtonDescription": "另外 {count} 个",
|
||||
"xpack.securitySolution.threatIntelligence.investigateInTimelineTitle": "在时间线中调查",
|
||||
"xpack.securitySolution.threatMatch.andDescription": "且",
|
||||
"xpack.securitySolution.threatMatch.fieldDescription": "字段",
|
||||
"xpack.securitySolution.threatMatch.fieldPlaceholderDescription": "搜索",
|
||||
|
|
|
@ -71,7 +71,7 @@ get:
|
|||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: string
|
||||
alerts:
|
||||
type: object
|
||||
description: >
|
||||
|
@ -119,7 +119,7 @@ get:
|
|||
description: >
|
||||
A secondary alias.
|
||||
It is typically used to support the signals alias for detection rules.
|
||||
shouldWrite:
|
||||
shouldWrite:
|
||||
type: boolean
|
||||
description: >
|
||||
Indicates whether the rule should write out alerts as data.
|
||||
|
@ -212,7 +212,7 @@ get:
|
|||
all:
|
||||
type: boolean
|
||||
read:
|
||||
type: boolean
|
||||
type: boolean
|
||||
category:
|
||||
type: string
|
||||
description: The rule category, which is used by features such as category-specific maintenance windows.
|
||||
|
@ -234,7 +234,7 @@ get:
|
|||
description: Indicates whether the rule type has custom mappings for the alert data.
|
||||
has_fields_for_a_a_d:
|
||||
type: boolean
|
||||
id:
|
||||
id:
|
||||
description: The unique identifier for the rule type.
|
||||
type: string
|
||||
is_exportable:
|
||||
|
@ -270,4 +270,4 @@ get:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '../components/schemas/401_response.yaml'
|
||||
$ref: '../components/schemas/401_response.yaml'
|
||||
|
|
|
@ -69,7 +69,7 @@ describe('fleet authz', () => {
|
|||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
siem: endpointCapabilities,
|
||||
siemV2: endpointCapabilities,
|
||||
transform: transformCapabilities,
|
||||
});
|
||||
|
||||
|
@ -95,7 +95,7 @@ describe('fleet authz', () => {
|
|||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
siem: endpointExceptionsCapabilities,
|
||||
siemV2: endpointExceptionsCapabilities,
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
|
@ -120,7 +120,7 @@ describe('fleet authz', () => {
|
|||
navLinks: {},
|
||||
management: {},
|
||||
catalogue: {},
|
||||
siem: endpointCapabilities,
|
||||
siemV2: endpointCapabilities,
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
|
|
|
@ -178,7 +178,7 @@ export function calculatePackagePrivilegesFromCapabilities(
|
|||
(acc, [privilege, { privilegeName }]) => {
|
||||
acc[privilege] = {
|
||||
executePackageAction:
|
||||
(capabilities.siem && (capabilities.siem[privilegeName] as boolean)) || false,
|
||||
(capabilities.siemV2 && (capabilities.siemV2[privilegeName] as boolean)) || false,
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
|
@ -208,14 +208,14 @@ export function calculatePackagePrivilegesFromCapabilities(
|
|||
export function calculateEndpointExceptionsPrivilegesFromCapabilities(
|
||||
capabilities: Capabilities | undefined
|
||||
): FleetAuthz['endpointExceptionsPrivileges'] {
|
||||
if (!capabilities || !capabilities.siem) {
|
||||
if (!capabilities || !capabilities.siemV2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const endpointExceptionsActions = Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce<
|
||||
Record<string, boolean>
|
||||
>((acc, privilegeName) => {
|
||||
acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false;
|
||||
acc[privilegeName] = (capabilities.siemV2[privilegeName] as boolean) || false;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { deepFreeze } from '@kbn/std';
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
|
||||
|
||||
const SECURITY_SOLUTION_APP_ID = 'siem';
|
||||
const SECURITY_SOLUTION_APP_ID = 'siemV2';
|
||||
|
||||
export interface PrivilegeMapObject {
|
||||
appId: string;
|
||||
|
|
|
@ -9,5 +9,7 @@ export { securityDefaultProductFeaturesConfig } from './src/security/product_fea
|
|||
export { getCasesDefaultProductFeaturesConfig } from './src/cases/product_feature_config';
|
||||
export { assistantDefaultProductFeaturesConfig } from './src/assistant/product_feature_config';
|
||||
export { attackDiscoveryDefaultProductFeaturesConfig } from './src/attack_discovery/product_feature_config';
|
||||
export { timelineDefaultProductFeaturesConfig } from './src/timeline/product_feature_config';
|
||||
export { notesDefaultProductFeaturesConfig } from './src/notes/product_feature_config';
|
||||
|
||||
export { createEnabledProductFeaturesConfigMap } from './src/helpers';
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { getSecurityFeature } from './src/security';
|
||||
export { getSecurityFeature, getSecurityV2Feature } from './src/security';
|
||||
export { getCasesFeature, getCasesV2Feature } from './src/cases';
|
||||
export { getAssistantFeature } from './src/assistant';
|
||||
export { getAttackDiscoveryFeature } from './src/attack_discovery';
|
||||
export { getTimelineFeature } from './src/timeline';
|
||||
export { getNotesFeature } from './src/notes';
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
export const APP_ID = 'securitySolution' as const;
|
||||
export const SERVER_APP_ID = 'siem' as const;
|
||||
|
||||
// New version created in 8.18. It was previously `SERVER_APP_ID`.
|
||||
export const SECURITY_FEATURE_ID_V2 = 'siemV2' as const;
|
||||
|
||||
/**
|
||||
* @deprecated deprecated in 8.17. Use CASE_FEATURE_ID_V2 instead
|
||||
*/
|
||||
|
@ -21,6 +24,8 @@ export const SECURITY_SOLUTION_CASES_APP_ID = 'securitySolutionCases' as const;
|
|||
|
||||
export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const;
|
||||
export const ATTACK_DISCOVERY_FEATURE_ID = 'securitySolutionAttackDiscovery' as const;
|
||||
export const TIMELINE_FEATURE_ID = 'securitySolutionTimeline' as const;
|
||||
export const NOTES_FEATURE_ID = 'securitySolutionNotes' as const;
|
||||
|
||||
// Same as the plugin id defined by Cloud Security Posture
|
||||
export const CLOUD_POSTURE_APP_ID = 'csp' as const;
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { getNotesBaseKibanaFeature } from './kibana_features';
|
||||
import type { ProductFeatureParams } from '../types';
|
||||
import type { SecurityFeatureParams } from '../security/types';
|
||||
|
||||
export const getNotesFeature = (params: SecurityFeatureParams): ProductFeatureParams => ({
|
||||
baseKibanaFeature: getNotesBaseKibanaFeature(params),
|
||||
baseKibanaSubFeatureIds: [],
|
||||
subFeaturesMap: new Map(),
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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-application-common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
|
||||
|
||||
import { APP_ID, NOTES_FEATURE_ID } from '../constants';
|
||||
import { type BaseKibanaFeatureConfig } from '../types';
|
||||
import type { SecurityFeatureParams } from '../security/types';
|
||||
|
||||
export const getNotesBaseKibanaFeature = (
|
||||
params: SecurityFeatureParams
|
||||
): BaseKibanaFeatureConfig => ({
|
||||
id: NOTES_FEATURE_ID,
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionNotesTitle',
|
||||
{
|
||||
defaultMessage: 'Notes',
|
||||
}
|
||||
),
|
||||
order: 1100,
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
|
||||
app: [NOTES_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
privileges: {
|
||||
all: {
|
||||
app: [NOTES_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
savedObject: {
|
||||
all: params.savedObjects,
|
||||
read: params.savedObjects,
|
||||
},
|
||||
ui: ['read', 'crud'],
|
||||
api: ['notes_read', 'notes_write'],
|
||||
},
|
||||
read: {
|
||||
app: [NOTES_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: params.savedObjects,
|
||||
},
|
||||
ui: ['read'],
|
||||
api: ['notes_read'],
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { ProductFeatureNotesFeatureKey } from '../product_features_keys';
|
||||
import type { ProductFeatureKibanaConfig } from '../types';
|
||||
|
||||
/**
|
||||
* App features privileges configuration for the notes feature.
|
||||
* These are the configs that are shared between both offering types (ess and serverless).
|
||||
* They can be extended on each offering plugin to register privileges using different way on each offering type.
|
||||
*
|
||||
* Privileges can be added in different ways:
|
||||
* - `privileges`: the privileges that will be added directly into the main Security feature.
|
||||
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
|
||||
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
|
||||
*/
|
||||
export const notesDefaultProductFeaturesConfig: Record<
|
||||
ProductFeatureNotesFeatureKey,
|
||||
ProductFeatureKibanaConfig
|
||||
> = {
|
||||
[ProductFeatureNotesFeatureKey.notes]: {
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['notes_read', 'notes_write'],
|
||||
ui: ['read', 'crud'],
|
||||
},
|
||||
read: {
|
||||
api: ['notes_read'],
|
||||
ui: ['read'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -114,19 +114,37 @@ export enum ProductFeatureAttackDiscoveryKey {
|
|||
attackDiscovery = 'attack_discovery',
|
||||
}
|
||||
|
||||
export enum ProductFeatureTimelineFeatureKey {
|
||||
/**
|
||||
* Enables Timeline
|
||||
*/
|
||||
timeline = 'timeline',
|
||||
}
|
||||
|
||||
export enum ProductFeatureNotesFeatureKey {
|
||||
/**
|
||||
* Enables Notes
|
||||
*/
|
||||
notes = 'notes',
|
||||
}
|
||||
|
||||
// Merges the two enums.
|
||||
export const ProductFeatureKey = {
|
||||
...ProductFeatureSecurityKey,
|
||||
...ProductFeatureCasesKey,
|
||||
...ProductFeatureAssistantKey,
|
||||
...ProductFeatureAttackDiscoveryKey,
|
||||
...ProductFeatureTimelineFeatureKey,
|
||||
...ProductFeatureNotesFeatureKey,
|
||||
};
|
||||
// We need to merge the value and the type and export both to replicate how enum works.
|
||||
export type ProductFeatureKeyType =
|
||||
| ProductFeatureSecurityKey
|
||||
| ProductFeatureCasesKey
|
||||
| ProductFeatureAssistantKey
|
||||
| ProductFeatureAttackDiscoveryKey;
|
||||
| ProductFeatureAttackDiscoveryKey
|
||||
| ProductFeatureTimelineFeatureKey
|
||||
| ProductFeatureNotesFeatureKey;
|
||||
|
||||
export const ALL_PRODUCT_FEATURE_KEYS = Object.freeze(Object.values(ProductFeatureKey));
|
||||
|
||||
|
|
|
@ -6,13 +6,21 @@
|
|||
*/
|
||||
import type { SecuritySubFeatureId } from '../product_features_keys';
|
||||
import type { ProductFeatureParams } from '../types';
|
||||
import { getSecurityBaseKibanaFeature } from './kibana_features';
|
||||
import { getSecurityBaseKibanaFeature } from './v1_features/kibana_features';
|
||||
import {
|
||||
getSecuritySubFeaturesMap,
|
||||
getSecurityBaseKibanaSubFeatureIds,
|
||||
} from './kibana_sub_features';
|
||||
} from './v1_features/kibana_sub_features';
|
||||
import { getSecurityV2BaseKibanaFeature } from './v2_features/kibana_features';
|
||||
import {
|
||||
getSecurityV2SubFeaturesMap,
|
||||
getSecurityV2BaseKibanaSubFeatureIds,
|
||||
} from './v2_features/kibana_sub_features';
|
||||
import type { SecurityFeatureParams } from './types';
|
||||
|
||||
/**
|
||||
* @deprecated Use getSecurityV2Feature instead
|
||||
*/
|
||||
export const getSecurityFeature = (
|
||||
params: SecurityFeatureParams
|
||||
): ProductFeatureParams<SecuritySubFeatureId> => ({
|
||||
|
@ -20,3 +28,11 @@ export const getSecurityFeature = (
|
|||
baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params),
|
||||
subFeaturesMap: getSecuritySubFeaturesMap(params),
|
||||
});
|
||||
|
||||
export const getSecurityV2Feature = (
|
||||
params: SecurityFeatureParams
|
||||
): ProductFeatureParams<SecuritySubFeatureId> => ({
|
||||
baseKibanaFeature: getSecurityV2BaseKibanaFeature(params),
|
||||
baseKibanaSubFeatureIds: getSecurityV2BaseKibanaSubFeatureIds(params),
|
||||
subFeaturesMap: getSecurityV2SubFeaturesMap(params),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 { KibanaFeatureScope } from '@kbn/features-plugin/common';
|
||||
|
||||
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
|
||||
import {
|
||||
EQL_RULE_TYPE_ID,
|
||||
ESQL_RULE_TYPE_ID,
|
||||
INDICATOR_RULE_TYPE_ID,
|
||||
ML_RULE_TYPE_ID,
|
||||
NEW_TERMS_RULE_TYPE_ID,
|
||||
QUERY_RULE_TYPE_ID,
|
||||
SAVED_QUERY_RULE_TYPE_ID,
|
||||
THRESHOLD_RULE_TYPE_ID,
|
||||
} from '@kbn/securitysolution-rules';
|
||||
import {
|
||||
APP_ID,
|
||||
SERVER_APP_ID,
|
||||
LEGACY_NOTIFICATIONS_ID,
|
||||
CLOUD_POSTURE_APP_ID,
|
||||
CLOUD_DEFEND_APP_ID,
|
||||
SECURITY_FEATURE_ID_V2,
|
||||
TIMELINE_FEATURE_ID,
|
||||
NOTES_FEATURE_ID,
|
||||
} from '../../constants';
|
||||
import type { SecurityFeatureParams } from '../types';
|
||||
import type { BaseKibanaFeatureConfig } from '../../types';
|
||||
|
||||
const SECURITY_RULE_TYPES = [
|
||||
LEGACY_NOTIFICATIONS_ID,
|
||||
ESQL_RULE_TYPE_ID,
|
||||
EQL_RULE_TYPE_ID,
|
||||
INDICATOR_RULE_TYPE_ID,
|
||||
ML_RULE_TYPE_ID,
|
||||
QUERY_RULE_TYPE_ID,
|
||||
SAVED_QUERY_RULE_TYPE_ID,
|
||||
THRESHOLD_RULE_TYPE_ID,
|
||||
NEW_TERMS_RULE_TYPE_ID,
|
||||
];
|
||||
|
||||
const alertingFeatures = SECURITY_RULE_TYPES.map((ruleTypeId) => ({
|
||||
ruleTypeId,
|
||||
consumers: [SERVER_APP_ID],
|
||||
}));
|
||||
|
||||
export const getSecurityBaseKibanaFeature = ({
|
||||
savedObjects,
|
||||
}: SecurityFeatureParams): BaseKibanaFeatureConfig => ({
|
||||
deprecated: {
|
||||
notice: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionSecurity.deprecationMessage',
|
||||
{
|
||||
defaultMessage: 'The {currentId} permissions are deprecated, please see {idV2}.',
|
||||
values: {
|
||||
currentId: SERVER_APP_ID,
|
||||
idV2: SECURITY_FEATURE_ID_V2,
|
||||
},
|
||||
}
|
||||
),
|
||||
},
|
||||
|
||||
id: SERVER_APP_ID,
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionTitleDeprecated',
|
||||
{
|
||||
defaultMessage: 'Security (Deprecated)',
|
||||
}
|
||||
),
|
||||
order: 1100,
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
management: {
|
||||
insightsAndAlerting: ['triggersActions'],
|
||||
},
|
||||
alerting: alertingFeatures,
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.securityGroupDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
"Each sub-feature privilege in this group must be assigned individually. Global assignment is only supported if your pricing plan doesn't allow individual feature privileges.",
|
||||
}
|
||||
),
|
||||
privileges: {
|
||||
all: {
|
||||
replacedBy: {
|
||||
default: [
|
||||
{ feature: TIMELINE_FEATURE_ID, privileges: ['all'] },
|
||||
{ feature: NOTES_FEATURE_ID, privileges: ['all'] },
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['all'] },
|
||||
],
|
||||
minimal: [
|
||||
{ feature: TIMELINE_FEATURE_ID, privileges: ['all'] },
|
||||
{ feature: NOTES_FEATURE_ID, privileges: ['all'] },
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['minimal_all'] },
|
||||
],
|
||||
},
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
api: [
|
||||
APP_ID,
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
'rac',
|
||||
'cloud-security-posture-all',
|
||||
'cloud-security-posture-read',
|
||||
'cloud-defend-all',
|
||||
'cloud-defend-read',
|
||||
'timeline_write',
|
||||
'timeline_read',
|
||||
'notes_write',
|
||||
'notes_read',
|
||||
],
|
||||
savedObject: {
|
||||
all: ['alert', ...savedObjects],
|
||||
read: [],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
all: alertingFeatures,
|
||||
},
|
||||
alert: {
|
||||
all: alertingFeatures,
|
||||
},
|
||||
},
|
||||
management: {
|
||||
insightsAndAlerting: ['triggersActions'],
|
||||
},
|
||||
ui: ['show', 'crud'],
|
||||
},
|
||||
read: {
|
||||
replacedBy: {
|
||||
default: [
|
||||
{ feature: TIMELINE_FEATURE_ID, privileges: ['read'] },
|
||||
{ feature: NOTES_FEATURE_ID, privileges: ['read'] },
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['read'] },
|
||||
],
|
||||
minimal: [
|
||||
{ feature: TIMELINE_FEATURE_ID, privileges: ['read'] },
|
||||
{ feature: NOTES_FEATURE_ID, privileges: ['read'] },
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['minimal_read'] },
|
||||
],
|
||||
},
|
||||
app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
api: [
|
||||
APP_ID,
|
||||
'lists-read',
|
||||
'rac',
|
||||
'cloud-security-posture-read',
|
||||
'cloud-defend-read',
|
||||
'timeline_read',
|
||||
'notes_read',
|
||||
],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [...savedObjects],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
read: alertingFeatures,
|
||||
},
|
||||
alert: {
|
||||
all: alertingFeatures,
|
||||
},
|
||||
},
|
||||
management: {
|
||||
insightsAndAlerting: ['triggersActions'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,755 @@
|
|||
/*
|
||||
* 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 { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants';
|
||||
import {
|
||||
ProductFeaturesPrivilegeId,
|
||||
ProductFeaturesPrivileges,
|
||||
} from '../../product_features_privileges';
|
||||
|
||||
import { SecuritySubFeatureId } from '../../product_features_keys';
|
||||
import { APP_ID, SECURITY_FEATURE_ID_V2 } from '../../constants';
|
||||
import type { SecurityFeatureParams } from '../types';
|
||||
|
||||
const endpointListSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Endpoint List access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList',
|
||||
{
|
||||
defaultMessage: 'Endpoint List',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Displays all hosts running Elastic Defend and their relevant integration details.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['endpoint_list_all'] }],
|
||||
api: [`${APP_ID}-writeEndpointList`, `${APP_ID}-readEndpointList`],
|
||||
id: 'endpoint_list_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeEndpointList', 'readEndpointList'],
|
||||
},
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['endpoint_list_read'] }],
|
||||
api: [`${APP_ID}-readEndpointList`],
|
||||
id: 'endpoint_list_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readEndpointList'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const trustedApplicationsSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Trusted Applications access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications',
|
||||
{
|
||||
defaultMessage: 'Trusted Applications',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Helps mitigate conflicts with other software, usually other antivirus or endpoint security applications.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['trusted_applications_all'] },
|
||||
],
|
||||
api: [
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
`${APP_ID}-writeTrustedApplications`,
|
||||
`${APP_ID}-readTrustedApplications`,
|
||||
],
|
||||
id: 'trusted_applications_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [EXCEPTION_LIST_NAMESPACE_AGNOSTIC],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeTrustedApplications', 'readTrustedApplications'],
|
||||
},
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['trusted_applications_read'] },
|
||||
],
|
||||
api: ['lists-read', 'lists-summary', `${APP_ID}-readTrustedApplications`],
|
||||
id: 'trusted_applications_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readTrustedApplications'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const hostIsolationExceptionsBasicSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Host Isolation Exceptions access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions',
|
||||
{
|
||||
defaultMessage: 'Host Isolation Exceptions',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Add specific IP addresses that isolated hosts are still allowed to communicate with, even when isolated from the rest of the network.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['host_isolation_exceptions_all'] },
|
||||
],
|
||||
api: [
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
`${APP_ID}-deleteHostIsolationExceptions`,
|
||||
`${APP_ID}-readHostIsolationExceptions`,
|
||||
],
|
||||
id: 'host_isolation_exceptions_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [EXCEPTION_LIST_NAMESPACE_AGNOSTIC],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readHostIsolationExceptions', 'deleteHostIsolationExceptions'],
|
||||
},
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['host_isolation_exceptions_read'] },
|
||||
],
|
||||
api: ['lists-read', 'lists-summary', `${APP_ID}-readHostIsolationExceptions`],
|
||||
id: 'host_isolation_exceptions_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readHostIsolationExceptions'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const blocklistSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.blockList.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Blocklist access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate('securitySolutionPackages.features.featureRegistry.subFeatures.blockList', {
|
||||
defaultMessage: 'Blocklist',
|
||||
}),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.blockList.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Extend Elastic Defend’s protection against malicious processes and protect against potentially harmful applications.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['blocklist_all'] }],
|
||||
api: [
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
`${APP_ID}-writeBlocklist`,
|
||||
`${APP_ID}-readBlocklist`,
|
||||
],
|
||||
id: 'blocklist_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [EXCEPTION_LIST_NAMESPACE_AGNOSTIC],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeBlocklist', 'readBlocklist'],
|
||||
},
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['blocklist_read'] }],
|
||||
api: ['lists-read', 'lists-summary', `${APP_ID}-readBlocklist`],
|
||||
id: 'blocklist_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readBlocklist'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const eventFiltersSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Event Filters access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters',
|
||||
{
|
||||
defaultMessage: 'Event Filters',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Filter out endpoint events that you do not need or want stored in Elasticsearch.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['event_filters_all'] }],
|
||||
api: [
|
||||
'lists-all',
|
||||
'lists-read',
|
||||
'lists-summary',
|
||||
`${APP_ID}-writeEventFilters`,
|
||||
`${APP_ID}-readEventFilters`,
|
||||
],
|
||||
id: 'event_filters_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [EXCEPTION_LIST_NAMESPACE_AGNOSTIC],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeEventFilters', 'readEventFilters'],
|
||||
},
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['event_filters_read'] }],
|
||||
api: ['lists-read', 'lists-summary', `${APP_ID}-readEventFilters`],
|
||||
id: 'event_filters_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readEventFilters'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const policyManagementSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Policy Management access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement',
|
||||
{
|
||||
defaultMessage: 'Elastic Defend Policy Management',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'Access the Elastic Defend integration policy to configure protections, event collection, and advanced policy features.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['policy_management_all'] }],
|
||||
api: [`${APP_ID}-writePolicyManagement`, `${APP_ID}-readPolicyManagement`],
|
||||
id: 'policy_management_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: ['policy-settings-protection-updates-note'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writePolicyManagement', 'readPolicyManagement'],
|
||||
},
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['policy_management_read'] }],
|
||||
api: [`${APP_ID}-readPolicyManagement`],
|
||||
id: 'policy_management_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['policy-settings-protection-updates-note'],
|
||||
},
|
||||
ui: ['readPolicyManagement'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const responseActionsHistorySubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Response Actions History access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory',
|
||||
{
|
||||
defaultMessage: 'Response Actions History',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory.description',
|
||||
{
|
||||
defaultMessage: 'Access the history of response actions performed on endpoints.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['actions_log_management_all'] },
|
||||
],
|
||||
api: [`${APP_ID}-writeActionsLogManagement`, `${APP_ID}-readActionsLogManagement`],
|
||||
id: 'actions_log_management_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeActionsLogManagement', 'readActionsLogManagement'],
|
||||
},
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['actions_log_management_read'] },
|
||||
],
|
||||
api: [`${APP_ID}-readActionsLogManagement`],
|
||||
id: 'actions_log_management_read',
|
||||
includeIn: 'none',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readActionsLogManagement'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const hostIsolationSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Host Isolation access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation',
|
||||
{
|
||||
defaultMessage: 'Host Isolation',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation.description',
|
||||
{ defaultMessage: 'Perform the "isolate" and "release" response actions.' }
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['host_isolation_all'] }],
|
||||
api: [`${APP_ID}-writeHostIsolationRelease`],
|
||||
id: 'host_isolation_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeHostIsolationRelease'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const processOperationsSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Process Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations',
|
||||
{
|
||||
defaultMessage: 'Process Operations',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations.description',
|
||||
{
|
||||
defaultMessage: 'Perform process-related response actions in the response console.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['process_operations_all'] }],
|
||||
api: [`${APP_ID}-writeProcessOperations`],
|
||||
id: 'process_operations_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeProcessOperations'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const fileOperationsSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for File Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations',
|
||||
{
|
||||
defaultMessage: 'File Operations',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations.description',
|
||||
{
|
||||
defaultMessage: 'Perform file-related response actions in the response console.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['file_operations_all'] }],
|
||||
api: [`${APP_ID}-writeFileOperations`],
|
||||
id: 'file_operations_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeFileOperations'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// execute operations are not available in 8.7,
|
||||
// but will be available in 8.8
|
||||
const executeActionSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Execute Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations',
|
||||
{
|
||||
defaultMessage: 'Execute Operations',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations.description',
|
||||
{
|
||||
defaultMessage: 'Perform script execution response actions in the response console.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['execute_operations_all'] }],
|
||||
api: [`${APP_ID}-writeExecuteOperations`],
|
||||
id: 'execute_operations_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeExecuteOperations'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 8.15 feature
|
||||
const scanActionSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.scanOperations.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Scan Operations access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.scanOperations',
|
||||
{
|
||||
defaultMessage: 'Scan Operations',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.scanOperations.description',
|
||||
{
|
||||
defaultMessage: 'Perform folder scan response actions in the response console.',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [{ feature: SECURITY_FEATURE_ID_V2, privileges: ['scan_operations_all'] }],
|
||||
|
||||
api: [`${APP_ID}-writeScanOperations`],
|
||||
id: 'scan_operations_all',
|
||||
includeIn: 'none',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeScanOperations'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const endpointExceptionsSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
privilegesTooltip: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions.privilegesTooltip',
|
||||
{
|
||||
defaultMessage: 'All Spaces is required for Endpoint Exceptions access.',
|
||||
}
|
||||
),
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions',
|
||||
{
|
||||
defaultMessage: 'Endpoint Exceptions',
|
||||
}
|
||||
),
|
||||
description: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions.description',
|
||||
{
|
||||
defaultMessage: 'Use Endpoint Exceptions (this is a test sub-feature).',
|
||||
}
|
||||
),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['endpoint_exceptions_all'] },
|
||||
],
|
||||
id: 'endpoint_exceptions_all',
|
||||
includeIn: 'all',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].all,
|
||||
},
|
||||
{
|
||||
replacedBy: [
|
||||
{ feature: SECURITY_FEATURE_ID_V2, privileges: ['endpoint_exceptions_read'] },
|
||||
],
|
||||
id: 'endpoint_exceptions_read',
|
||||
includeIn: 'read',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].read,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Sub-features that will always be available for Security
|
||||
* regardless of the product type.
|
||||
*/
|
||||
export const getSecurityBaseKibanaSubFeatureIds = (
|
||||
{ experimentalFeatures }: SecurityFeatureParams // currently un-used, but left here as a convenience for possible future use
|
||||
): SecuritySubFeatureId[] => [SecuritySubFeatureId.hostIsolation];
|
||||
|
||||
/**
|
||||
* Defines all the Security Assistant subFeatures available.
|
||||
* The order of the subFeatures is the order they will be displayed
|
||||
*/
|
||||
|
||||
export const getSecuritySubFeaturesMap = ({
|
||||
experimentalFeatures,
|
||||
}: SecurityFeatureParams): Map<SecuritySubFeatureId, SubFeatureConfig> => {
|
||||
const enableSpaceAwarenessIfNeeded = (subFeature: SubFeatureConfig): SubFeatureConfig => {
|
||||
if (experimentalFeatures.endpointManagementSpaceAwarenessEnabled) {
|
||||
subFeature.requireAllSpaces = false;
|
||||
subFeature.privilegesTooltip = undefined;
|
||||
}
|
||||
|
||||
return subFeature;
|
||||
};
|
||||
|
||||
const securitySubFeaturesList: Array<[SecuritySubFeatureId, SubFeatureConfig]> = [
|
||||
[SecuritySubFeatureId.endpointList, enableSpaceAwarenessIfNeeded(endpointListSubFeature())],
|
||||
[
|
||||
SecuritySubFeatureId.endpointExceptions,
|
||||
enableSpaceAwarenessIfNeeded(endpointExceptionsSubFeature()),
|
||||
],
|
||||
[
|
||||
SecuritySubFeatureId.trustedApplications,
|
||||
enableSpaceAwarenessIfNeeded(trustedApplicationsSubFeature()),
|
||||
],
|
||||
[
|
||||
SecuritySubFeatureId.hostIsolationExceptionsBasic,
|
||||
enableSpaceAwarenessIfNeeded(hostIsolationExceptionsBasicSubFeature()),
|
||||
],
|
||||
[SecuritySubFeatureId.blocklist, enableSpaceAwarenessIfNeeded(blocklistSubFeature())],
|
||||
[SecuritySubFeatureId.eventFilters, enableSpaceAwarenessIfNeeded(eventFiltersSubFeature())],
|
||||
[
|
||||
SecuritySubFeatureId.policyManagement,
|
||||
enableSpaceAwarenessIfNeeded(policyManagementSubFeature()),
|
||||
],
|
||||
[
|
||||
SecuritySubFeatureId.responseActionsHistory,
|
||||
enableSpaceAwarenessIfNeeded(responseActionsHistorySubFeature()),
|
||||
],
|
||||
[SecuritySubFeatureId.hostIsolation, enableSpaceAwarenessIfNeeded(hostIsolationSubFeature())],
|
||||
[
|
||||
SecuritySubFeatureId.processOperations,
|
||||
enableSpaceAwarenessIfNeeded(processOperationsSubFeature()),
|
||||
],
|
||||
[SecuritySubFeatureId.fileOperations, enableSpaceAwarenessIfNeeded(fileOperationsSubFeature())],
|
||||
[SecuritySubFeatureId.executeAction, enableSpaceAwarenessIfNeeded(executeActionSubFeature())],
|
||||
[SecuritySubFeatureId.scanAction, enableSpaceAwarenessIfNeeded(scanActionSubFeature())],
|
||||
];
|
||||
|
||||
// Use the following code to add feature based on feature flag
|
||||
// if (experimentalFeatures.featureFlagName) {
|
||||
// securitySubFeaturesList.push([SecuritySubFeatureId.featureId, featureSubFeature]);
|
||||
// }
|
||||
|
||||
const securitySubFeaturesMap = new Map<SecuritySubFeatureId, SubFeatureConfig>(
|
||||
securitySubFeaturesList
|
||||
);
|
||||
|
||||
return Object.freeze(securitySubFeaturesMap);
|
||||
};
|
|
@ -19,15 +19,16 @@ import {
|
|||
SAVED_QUERY_RULE_TYPE_ID,
|
||||
THRESHOLD_RULE_TYPE_ID,
|
||||
} from '@kbn/securitysolution-rules';
|
||||
import type { BaseKibanaFeatureConfig } from '../types';
|
||||
import {
|
||||
APP_ID,
|
||||
SERVER_APP_ID,
|
||||
SECURITY_FEATURE_ID_V2,
|
||||
LEGACY_NOTIFICATIONS_ID,
|
||||
CLOUD_POSTURE_APP_ID,
|
||||
CLOUD_DEFEND_APP_ID,
|
||||
} from '../constants';
|
||||
import type { SecurityFeatureParams } from './types';
|
||||
SERVER_APP_ID,
|
||||
} from '../../constants';
|
||||
import type { SecurityFeatureParams } from '../types';
|
||||
import type { BaseKibanaFeatureConfig } from '../../types';
|
||||
|
||||
const SECURITY_RULE_TYPES = [
|
||||
LEGACY_NOTIFICATIONS_ID,
|
||||
|
@ -46,10 +47,10 @@ const alertingFeatures = SECURITY_RULE_TYPES.map((ruleTypeId) => ({
|
|||
consumers: [SERVER_APP_ID],
|
||||
}));
|
||||
|
||||
export const getSecurityBaseKibanaFeature = ({
|
||||
export const getSecurityV2BaseKibanaFeature = ({
|
||||
savedObjects,
|
||||
}: SecurityFeatureParams): BaseKibanaFeatureConfig => ({
|
||||
id: SERVER_APP_ID,
|
||||
id: SECURITY_FEATURE_ID_V2,
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionTitle',
|
||||
{
|
|
@ -11,11 +11,11 @@ import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-co
|
|||
import {
|
||||
ProductFeaturesPrivilegeId,
|
||||
ProductFeaturesPrivileges,
|
||||
} from '../product_features_privileges';
|
||||
} from '../../product_features_privileges';
|
||||
|
||||
import { SecuritySubFeatureId } from '../product_features_keys';
|
||||
import { APP_ID } from '../constants';
|
||||
import type { SecurityFeatureParams } from './types';
|
||||
import { SecuritySubFeatureId } from '../../product_features_keys';
|
||||
import { APP_ID } from '../../constants';
|
||||
import type { SecurityFeatureParams } from '../types';
|
||||
|
||||
const endpointListSubFeature = (): SubFeatureConfig => ({
|
||||
requireAllSpaces: true,
|
||||
|
@ -701,7 +701,7 @@ const endpointExceptionsSubFeature = (): SubFeatureConfig => ({
|
|||
* Sub-features that will always be available for Security
|
||||
* regardless of the product type.
|
||||
*/
|
||||
export const getSecurityBaseKibanaSubFeatureIds = (
|
||||
export const getSecurityV2BaseKibanaSubFeatureIds = (
|
||||
{ experimentalFeatures }: SecurityFeatureParams // currently un-used, but left here as a convenience for possible future use
|
||||
): SecuritySubFeatureId[] => [SecuritySubFeatureId.hostIsolation];
|
||||
|
||||
|
@ -710,7 +710,7 @@ export const getSecurityBaseKibanaSubFeatureIds = (
|
|||
* The order of the subFeatures is the order they will be displayed
|
||||
*/
|
||||
|
||||
export const getSecuritySubFeaturesMap = ({
|
||||
export const getSecurityV2SubFeaturesMap = ({
|
||||
experimentalFeatures,
|
||||
}: SecurityFeatureParams): Map<SecuritySubFeatureId, SubFeatureConfig> => {
|
||||
const enableSpaceAwarenessIfNeeded = (subFeature: SubFeatureConfig): SubFeatureConfig => {
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { getTimelineBaseKibanaFeature } from './kibana_features';
|
||||
import type { ProductFeatureParams } from '../types';
|
||||
import type { SecurityFeatureParams } from '../security/types';
|
||||
|
||||
export const getTimelineFeature = (params: SecurityFeatureParams): ProductFeatureParams => ({
|
||||
baseKibanaFeature: getTimelineBaseKibanaFeature(params),
|
||||
baseKibanaSubFeatureIds: [],
|
||||
subFeaturesMap: new Map(),
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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-application-common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
|
||||
|
||||
import { APP_ID, TIMELINE_FEATURE_ID } from '../constants';
|
||||
import { type BaseKibanaFeatureConfig } from '../types';
|
||||
import type { SecurityFeatureParams } from '../security/types';
|
||||
|
||||
export const getTimelineBaseKibanaFeature = (
|
||||
params: SecurityFeatureParams
|
||||
): BaseKibanaFeatureConfig => ({
|
||||
id: TIMELINE_FEATURE_ID,
|
||||
name: i18n.translate(
|
||||
'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionTimelineTitle',
|
||||
{
|
||||
defaultMessage: 'Timeline',
|
||||
}
|
||||
),
|
||||
order: 1100,
|
||||
category: DEFAULT_APP_CATEGORIES.security,
|
||||
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
|
||||
app: [TIMELINE_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
privileges: {
|
||||
all: {
|
||||
app: [TIMELINE_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
savedObject: {
|
||||
all: params.savedObjects,
|
||||
read: params.savedObjects,
|
||||
},
|
||||
ui: ['read', 'crud'],
|
||||
api: ['timeline_read', 'timeline_write'],
|
||||
},
|
||||
read: {
|
||||
app: [TIMELINE_FEATURE_ID, 'kibana'],
|
||||
catalogue: [APP_ID],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: params.savedObjects,
|
||||
},
|
||||
ui: ['read'],
|
||||
api: ['timeline_read'],
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { ProductFeatureTimelineFeatureKey } from '../product_features_keys';
|
||||
import type { ProductFeatureKibanaConfig } from '../types';
|
||||
|
||||
/**
|
||||
* App features privileges configuration for the timeline feature.
|
||||
* These are the configs that are shared between both offering types (ess and serverless).
|
||||
* They can be extended on each offering plugin to register privileges using different way on each offering type.
|
||||
*
|
||||
* Privileges can be added in different ways:
|
||||
* - `privileges`: the privileges that will be added directly into the main Security feature.
|
||||
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
|
||||
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
|
||||
*/
|
||||
export const timelineDefaultProductFeaturesConfig: Record<
|
||||
ProductFeatureTimelineFeatureKey,
|
||||
ProductFeatureKibanaConfig
|
||||
> = {
|
||||
[ProductFeatureTimelineFeatureKey.timeline]: {
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['timeline_read', 'timeline_write'],
|
||||
ui: ['read', 'crud'],
|
||||
},
|
||||
read: {
|
||||
api: ['timeline_read'],
|
||||
ui: ['read'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -20,6 +20,8 @@ import type {
|
|||
AssistantSubFeatureId,
|
||||
CasesSubFeatureId,
|
||||
SecuritySubFeatureId,
|
||||
ProductFeatureTimelineFeatureKey,
|
||||
ProductFeatureNotesFeatureKey,
|
||||
} from './product_features_keys';
|
||||
|
||||
export type { ProductFeatureKeyType };
|
||||
|
@ -57,6 +59,16 @@ export type ProductFeaturesAttackDiscoveryConfig = Map<
|
|||
ProductFeatureKibanaConfig
|
||||
>;
|
||||
|
||||
export type ProductFeaturesTimelineConfig = Map<
|
||||
ProductFeatureTimelineFeatureKey,
|
||||
ProductFeatureKibanaConfig
|
||||
>;
|
||||
|
||||
export type ProductFeaturesNotesConfig = Map<
|
||||
ProductFeatureNotesFeatureKey,
|
||||
ProductFeatureKibanaConfig
|
||||
>;
|
||||
|
||||
export type AppSubFeaturesMap<T extends string = string> = Map<T, SubFeatureConfig>;
|
||||
|
||||
export interface ProductFeatureParams<T extends string = string> {
|
||||
|
|
|
@ -47,7 +47,7 @@ const getTestComponent =
|
|||
...coreStart.application,
|
||||
capabilities: {
|
||||
...coreStart.application.capabilities,
|
||||
siem: { crud: true },
|
||||
siemV2: { crud: true },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ const getWrapper =
|
|||
...coreStart.application,
|
||||
capabilities: {
|
||||
...coreStart.application.capabilities,
|
||||
siem: { crud: canUpdate },
|
||||
siemV2: { crud: canUpdate },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,7 +22,10 @@ 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 TIMELINE_FEATURE_ID = 'securitySolutionTimeline' as const;
|
||||
export const NOTES_FEATURE_ID = 'securitySolutionNotes' as const;
|
||||
export const SERVER_APP_ID = 'siem' as const;
|
||||
export const SECURITY_FEATURE_ID = 'siemV2' as const;
|
||||
export const APP_NAME = 'Security' as const;
|
||||
export const APP_ICON = 'securityAnalyticsApp' as const;
|
||||
export const APP_ICON_SOLUTION = 'logoSecurity' as const;
|
||||
|
@ -66,7 +69,6 @@ export const ENDPOINT_METRICS_INDEX = '.ds-metrics-endpoint.metrics-*' as const;
|
|||
export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true as const;
|
||||
export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000 as const; // ms
|
||||
export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100 as const;
|
||||
export const SECURITY_FEATURE_ID = 'Security' as const;
|
||||
export const SECURITY_TAG_NAME = 'Security Solution' as const;
|
||||
export const SECURITY_TAG_DESCRIPTION = 'Security Solution auto-generated tag' as const;
|
||||
export const DEFAULT_SPACE_ID = 'default' as const;
|
||||
|
|
|
@ -10,6 +10,7 @@ export {
|
|||
APP_ID,
|
||||
CASES_FEATURE_ID,
|
||||
SERVER_APP_ID,
|
||||
SECURITY_FEATURE_ID,
|
||||
APP_PATH,
|
||||
MANAGE_PATH,
|
||||
ADD_DATA_PATH,
|
||||
|
|
|
@ -27,10 +27,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["read", "read_alerts"],
|
||||
"siemV2": ["read", "read_alerts"],
|
||||
"securitySolutionAssistant": ["none"],
|
||||
"securitySolutionAttackDiscovery": ["none"],
|
||||
"securitySolutionCasesV2": ["read"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["read"]
|
||||
},
|
||||
|
@ -76,10 +78,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["read"],
|
||||
"securitySolutionNotes": ["read"],
|
||||
"actions": ["read"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
|
@ -125,10 +129,12 @@
|
|||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siem": ["all", "read_alerts", "crud_alerts"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
|
@ -146,7 +152,115 @@
|
|||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"siem": ["read"]
|
||||
"siemV2": ["read"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"timeline_none": {
|
||||
"name": "timeline_none",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
".lists*",
|
||||
".items*",
|
||||
".asset-criticality.asset-criticality-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
".alerts-security*",
|
||||
".preview.alerts-security*",
|
||||
".internal.preview.alerts-security*",
|
||||
".siem-signals-*"
|
||||
],
|
||||
"privileges": ["read", "write", "manage"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionNotes": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes_none": {
|
||||
"name": "notes_none",
|
||||
"elasticsearch": {
|
||||
"cluster": [],
|
||||
"indices": [
|
||||
{
|
||||
"names": [
|
||||
"apm-*-transaction*",
|
||||
"traces-apm*",
|
||||
"auditbeat-*",
|
||||
"endgame-*",
|
||||
"filebeat-*",
|
||||
"logs-*",
|
||||
"packetbeat-*",
|
||||
"winlogbeat-*",
|
||||
".lists*",
|
||||
".items*",
|
||||
".asset-criticality.asset-criticality-*"
|
||||
],
|
||||
"privileges": ["read", "write"]
|
||||
},
|
||||
{
|
||||
"names": [
|
||||
".alerts-security*",
|
||||
".preview.alerts-security*",
|
||||
".internal.preview.alerts-security*",
|
||||
".siem-signals-*"
|
||||
],
|
||||
"privileges": ["read", "write", "manage"]
|
||||
},
|
||||
{
|
||||
"names": ["metrics-endpoint.metadata_current_*", ".fleet-agents*", ".fleet-actions*"],
|
||||
"privileges": ["read"]
|
||||
}
|
||||
],
|
||||
"run_as": []
|
||||
},
|
||||
"kibana": [
|
||||
{
|
||||
"feature": {
|
||||
"ml": ["read"],
|
||||
"siemV2": ["all", "read_alerts", "crud_alerts"],
|
||||
"securitySolutionAssistant": ["all"],
|
||||
"securitySolutionAttackDiscovery": ["all"],
|
||||
"securitySolutionCasesV2": ["all"],
|
||||
"securitySolutionTimeline": ["all"],
|
||||
"actions": ["all"],
|
||||
"builtInAlerts": ["all"]
|
||||
},
|
||||
"spaces": ["*"],
|
||||
"base": []
|
||||
|
|
|
@ -30,6 +30,8 @@ export enum ROLES {
|
|||
hunter = 'hunter',
|
||||
hunter_no_actions = 'hunter_no_actions',
|
||||
no_risk_engine_privileges = 'no_risk_engine_privileges',
|
||||
timeline_none = 'timeline_none',
|
||||
notes_none = 'notes_none',
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -114,6 +114,7 @@ export interface ActionProps {
|
|||
toggleShowNotes?: () => void;
|
||||
width?: number;
|
||||
disablePinAction?: boolean;
|
||||
disableTimelineAction?: boolean;
|
||||
}
|
||||
|
||||
interface AdditionalControlColumnProps {
|
||||
|
|
|
@ -104,6 +104,54 @@ describe('createAddToTimelineCellAction', () => {
|
|||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if the user has read access to timeline', async () => {
|
||||
const factory = createAddToTimelineCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if the user does not have access to timeline', async () => {
|
||||
const factory = createAddToTimelineCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import type { KBN_FIELD_TYPES } from '@kbn/field-types';
|
|||
import { addProvider } from '../../../../timelines/store/actions';
|
||||
import { TimelineId } from '../../../../../common/types';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
import { extractTimelineCapabilities } from '../../../../common/utils/timeline_capabilities';
|
||||
import { fieldHasCellActions } from '../../utils';
|
||||
import {
|
||||
ADD_TO_TIMELINE,
|
||||
|
@ -38,17 +39,22 @@ export const createAddToTimelineCellActionFactory = createCellActionFactory(
|
|||
store: SecurityAppStore;
|
||||
services: StartServices;
|
||||
}): CellActionTemplate<SecurityCellAction> => {
|
||||
const { notifications: notificationsService } = services;
|
||||
|
||||
const {
|
||||
notifications: notificationsService,
|
||||
application: { capabilities },
|
||||
} = services;
|
||||
const timelineCapabilities = extractTimelineCapabilities(capabilities);
|
||||
return {
|
||||
type: SecurityCellActionType.ADD_TO_TIMELINE,
|
||||
getIconType: () => ADD_TO_TIMELINE_ICON,
|
||||
getDisplayName: () => ADD_TO_TIMELINE,
|
||||
getDisplayNameTooltip: () => ADD_TO_TIMELINE,
|
||||
isCompatible: async ({ data }) => {
|
||||
|
||||
isCompatible: async ({ data, metadata }) => {
|
||||
const field = data[0]?.field;
|
||||
|
||||
return (
|
||||
timelineCapabilities.read &&
|
||||
data.length === 1 && // TODO Add support for multiple values
|
||||
fieldHasCellActions(field.name) &&
|
||||
isValidDataProviderField(field.name, field.type) &&
|
||||
|
|
|
@ -107,6 +107,52 @@ describe('createAddToNewTimelineCellAction', () => {
|
|||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if the the user has read access to timeline', async () => {
|
||||
const factory = createInvestigateInNewTimelineCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return flase if the user does not have access to timeline', async () => {
|
||||
const factory = createInvestigateInNewTimelineCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import { addProvider, showTimeline } from '../../../../timelines/store/actions';
|
|||
import { TimelineId } from '../../../../../common/types';
|
||||
import type { SecurityAppStore } from '../../../../common/store';
|
||||
import { fieldHasCellActions } from '../../utils';
|
||||
import { extractTimelineCapabilities } from '../../../../common/utils/timeline_capabilities';
|
||||
import {
|
||||
ADD_TO_TIMELINE_FAILED_TEXT,
|
||||
ADD_TO_TIMELINE_FAILED_TITLE,
|
||||
|
@ -39,7 +40,11 @@ export const createInvestigateInNewTimelineCellActionFactory = createCellActionF
|
|||
store: SecurityAppStore;
|
||||
services: StartServices;
|
||||
}): CellActionTemplate<SecurityCellAction> => {
|
||||
const { notifications: notificationsService } = services;
|
||||
const {
|
||||
notifications: notificationsService,
|
||||
application: { capabilities },
|
||||
} = services;
|
||||
const timelineCapabilities = extractTimelineCapabilities(capabilities);
|
||||
|
||||
return {
|
||||
type: SecurityCellActionType.INVESTIGATE_IN_NEW_TIMELINE,
|
||||
|
@ -50,6 +55,7 @@ export const createInvestigateInNewTimelineCellActionFactory = createCellActionF
|
|||
const field = data[0]?.field;
|
||||
|
||||
return (
|
||||
timelineCapabilities.read &&
|
||||
data.length === 1 && // TODO Add support for multiple values
|
||||
fieldHasCellActions(field.name) &&
|
||||
isValidDataProviderField(field.name, field.type) &&
|
||||
|
|
|
@ -100,6 +100,54 @@ describe('createAddToTimelineDiscoverCellActionFactory', () => {
|
|||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if the user has read access to timeline', async () => {
|
||||
const factory = createAddToTimelineDiscoverCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return false if the user does not have access to timeline', async () => {
|
||||
const factory = createAddToTimelineDiscoverCellActionFactory({
|
||||
store,
|
||||
services: {
|
||||
...services,
|
||||
application: {
|
||||
...services.application,
|
||||
capabilities: {
|
||||
...services.application.capabilities,
|
||||
securitySolutionTimeline: {
|
||||
read: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const addToTimelineActionIsCompatible = factory({
|
||||
id: 'testAddToTimeline',
|
||||
order: 1,
|
||||
});
|
||||
|
||||
expect(await addToTimelineActionIsCompatible.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute', () => {
|
||||
|
|
|
@ -163,8 +163,26 @@ describe('createAddToTimelineLensAction', () => {
|
|||
expect(await addToTimelineAction.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true if everything is okay', async () => {
|
||||
expect(await addToTimelineAction.isCompatible(context)).toEqual(true);
|
||||
it('should return false when the user does not have access to timeline', async () => {
|
||||
(
|
||||
KibanaServices.get().application.capabilities.securitySolutionTimeline as {
|
||||
crud: boolean;
|
||||
read: boolean;
|
||||
}
|
||||
).read = false;
|
||||
const _action = createAddToTimelineLensAction({ store, order: 1 });
|
||||
expect(await _action.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return true when the user has read access to timeline', async () => {
|
||||
(
|
||||
KibanaServices.get().application.capabilities.securitySolutionTimeline as {
|
||||
crud: boolean;
|
||||
read: boolean;
|
||||
}
|
||||
).read = true;
|
||||
const _action = createAddToTimelineLensAction({ store, order: 1 });
|
||||
expect(await _action.isCompatible(context)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { KibanaServices } from '../../../../common/lib/kibana';
|
|||
import type { SecurityAppStore } from '../../../../common/store/types';
|
||||
import { addProvider } from '../../../../timelines/store/actions';
|
||||
import type { DataProvider } from '../../../../../common/types';
|
||||
import { extractTimelineCapabilities } from '../../../../common/utils/timeline_capabilities';
|
||||
import { EXISTS_OPERATOR, TimelineId } from '../../../../../common/types';
|
||||
import { fieldHasCellActions, isInSecurityApp } from '../../utils';
|
||||
import {
|
||||
|
@ -75,6 +76,7 @@ export const createAddToTimelineLensAction = ({
|
|||
applicationService.currentAppId$.subscribe((appId) => {
|
||||
currentAppId = appId;
|
||||
});
|
||||
const timelineCapabilities = extractTimelineCapabilities(applicationService.capabilities);
|
||||
|
||||
return createAction<CellValueContext>({
|
||||
id: ACTION_ID,
|
||||
|
@ -83,6 +85,7 @@ export const createAddToTimelineLensAction = ({
|
|||
getIconType: () => ADD_TO_TIMELINE_ICON,
|
||||
getDisplayName: () => ADD_TO_TIMELINE,
|
||||
isCompatible: async ({ embeddable, data }) =>
|
||||
timelineCapabilities.read &&
|
||||
!hasBlockingError(embeddable) &&
|
||||
isLensApi(embeddable) &&
|
||||
apiPublishesUnifiedSearch(embeddable) &&
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
noCasesCapabilities,
|
||||
readCasesCapabilities,
|
||||
} from '../cases_test_utils';
|
||||
import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../common/constants';
|
||||
import { CASES_FEATURE_ID, SECURITY_FEATURE_ID } from '../../common/constants';
|
||||
|
||||
const mockNotFoundPage = jest.fn(() => null);
|
||||
jest.mock('./404', () => ({
|
||||
|
@ -33,7 +33,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to overview page when siem and case privileges are all', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: true, crud: true },
|
||||
[SECURITY_FEATURE_ID]: { show: true, crud: true },
|
||||
[CASES_FEATURE_ID]: allCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -42,7 +42,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to overview page when siem and case privileges are read', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: true, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: true, crud: false },
|
||||
[CASES_FEATURE_ID]: readCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -51,7 +51,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to not_found page when siem and case privileges are off', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: false, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: false, crud: false },
|
||||
[CASES_FEATURE_ID]: noCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -61,7 +61,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to overview page when siem privilege is read and case privilege is all', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: true, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: true, crud: false },
|
||||
[CASES_FEATURE_ID]: allCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -70,7 +70,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to overview page when siem privilege is read and case privilege is read', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: true, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: true, crud: false },
|
||||
[CASES_FEATURE_ID]: allCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -79,7 +79,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to cases page when siem privilege is none and case privilege is read', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: false, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: false, crud: false },
|
||||
[CASES_FEATURE_ID]: readCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
@ -88,7 +88,7 @@ describe('RedirectRoute', () => {
|
|||
|
||||
it('RedirectRoute should redirect to cases page when siem privilege is none and case privilege is all', () => {
|
||||
const mockCapabilities = {
|
||||
[SERVER_APP_ID]: { show: false, crud: false },
|
||||
[SECURITY_FEATURE_ID]: { show: false, crud: false },
|
||||
[CASES_FEATURE_ID]: allCasesCapabilities(),
|
||||
} as unknown as Capabilities;
|
||||
render(<RedirectRoute capabilities={mockCapabilities} />);
|
||||
|
|
|
@ -10,14 +10,10 @@ import type { RouteProps } from 'react-router-dom';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import {
|
||||
CASES_FEATURE_ID,
|
||||
CASES_PATH,
|
||||
ONBOARDING_PATH,
|
||||
SERVER_APP_ID,
|
||||
} from '../../common/constants';
|
||||
import { CASES_FEATURE_ID, CASES_PATH, ONBOARDING_PATH } from '../../common/constants';
|
||||
import { NotFoundPage } from './404';
|
||||
import type { StartServices } from '../types';
|
||||
import { hasAccessToSecuritySolution } from '../helpers_access';
|
||||
|
||||
export interface AppRoutesProps {
|
||||
services: StartServices;
|
||||
|
@ -37,7 +33,7 @@ export const AppRoutes: React.FC<AppRoutesProps> = React.memo(({ services, subPl
|
|||
AppRoutes.displayName = 'AppRoutes';
|
||||
|
||||
export const RedirectRoute = React.memo<{ capabilities: Capabilities }>(({ capabilities }) => {
|
||||
if (capabilities[SERVER_APP_ID].show === true) {
|
||||
if (hasAccessToSecuritySolution(capabilities)) {
|
||||
return <Redirect to={ONBOARDING_PATH} />;
|
||||
}
|
||||
if (capabilities[CASES_FEATURE_ID].read_cases === true) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { SecurityPageName, ExternalPageName } from '@kbn/security-solution-navigation';
|
||||
import { ASSETS_PATH, CLOUD_DEFEND_PATH } from '../../../../../common/constants';
|
||||
import { SERVER_APP_ID } from '../../../../../common';
|
||||
import { SECURITY_FEATURE_ID } from '../../../../../common';
|
||||
import type { LinkItem } from '../../../../common/links/types';
|
||||
import type { SolutionNavLink } from '../../../../common/links';
|
||||
import { IconEcctlLazy, IconFleetLazy } from './lazy_icons';
|
||||
|
@ -18,7 +18,7 @@ const assetsAppLink: LinkItem = {
|
|||
id: SecurityPageName.assets,
|
||||
title: i18n.ASSETS_TITLE,
|
||||
path: ASSETS_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
hideTimeline: true,
|
||||
skipUrlState: true,
|
||||
links: [], // endpoints and cloudDefend links are added in createAssetsLinkFromManage
|
||||
|
@ -30,7 +30,7 @@ const assetsCloudDefendAppLink: LinkItem = {
|
|||
title: i18n.CLOUD_DEFEND_TITLE,
|
||||
description: i18n.CLOUD_DEFEND_DESCRIPTION,
|
||||
path: CLOUD_DEFEND_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
landingIcon: IconEcctlLazy,
|
||||
isBeta: true,
|
||||
hideTimeline: true,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { ExternalPageName, SecurityPageName } from '@kbn/security-solution-navigation';
|
||||
import { INVESTIGATIONS_PATH } from '../../../../../common/constants';
|
||||
import { SERVER_APP_ID } from '../../../../../common';
|
||||
import { SECURITY_FEATURE_ID } from '../../../../../common';
|
||||
import type { LinkItem } from '../../../../common/links/types';
|
||||
import type { SolutionNavLink } from '../../../../common/links';
|
||||
import { IconOsqueryLazy, IconTimelineLazy } from './lazy_icons';
|
||||
|
@ -18,7 +18,7 @@ const investigationsAppLink: LinkItem = {
|
|||
id: SecurityPageName.investigations,
|
||||
title: i18n.INVESTIGATIONS_TITLE,
|
||||
path: INVESTIGATIONS_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
hideTimeline: true,
|
||||
skipUrlState: true,
|
||||
links: [], // timeline and note links are added via the methods below
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '@kbn/security-solution-navigation';
|
||||
import { MACHINE_LEARNING_PATH } from '../../../../../common/constants';
|
||||
import type { LinkItem } from '../../../../common/links/types';
|
||||
import { SERVER_APP_ID } from '../../../../../common';
|
||||
import { SECURITY_FEATURE_ID } from '../../../../../common';
|
||||
import type { SolutionLinkCategory, SolutionNavLink } from '../../../../common/links';
|
||||
import {
|
||||
IconLensLazy,
|
||||
|
@ -39,7 +39,7 @@ export const mlAppLink: LinkItem = {
|
|||
id: SecurityPageName.mlLanding,
|
||||
title: i18n.ML_TITLE,
|
||||
path: MACHINE_LEARNING_PATH,
|
||||
capabilities: [[`${SERVER_APP_ID}.show`, `ml.canGetJobs`]],
|
||||
capabilities: [[`${SECURITY_FEATURE_ID}.show`, `ml.canGetJobs`]],
|
||||
globalSearchKeywords: [i18n.ML_KEYWORD],
|
||||
hideTimeline: true,
|
||||
skipUrlState: true,
|
||||
|
|
|
@ -12,6 +12,7 @@ import type { Filter } from '@kbn/es-query';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useAssistantContext } from '@kbn/elastic-assistant';
|
||||
import { extractTimelineCapabilities } from '../../common/utils/timeline_capabilities';
|
||||
import { sourcererSelectors } from '../../common/store';
|
||||
import { sourcererActions } from '../../common/store/actions';
|
||||
import { inputsActions } from '../../common/store/inputs';
|
||||
|
@ -36,6 +37,7 @@ import { useDiscoverInTimelineContext } from '../../common/components/discover_i
|
|||
import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline';
|
||||
import { useSourcererDataView } from '../../sourcerer/containers';
|
||||
import { useDiscoverState } from '../../timelines/components/timeline/tabs/esql/use_discover_state';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
||||
export interface SendToTimelineButtonProps {
|
||||
asEmptyButton: boolean;
|
||||
|
@ -61,7 +63,10 @@ export const SendToTimelineButton: FC<PropsWithChildren<SendToTimelineButtonProp
|
|||
const { discoverStateContainer, defaultDiscoverAppState } = useDiscoverInTimelineContext();
|
||||
const { dataViewId: timelineDataViewId } = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const { setDiscoverAppState } = useDiscoverState();
|
||||
|
||||
const {
|
||||
application: { capabilities },
|
||||
} = useKibana().services;
|
||||
const { read: hasAccessToTimeline } = extractTimelineCapabilities(capabilities);
|
||||
const signalIndexName = useSelector(sourcererSelectors.signalIndexName);
|
||||
const defaultDataView = useSelector(sourcererSelectors.defaultDataView);
|
||||
|
||||
|
@ -242,7 +247,7 @@ export const SendToTimelineButton: FC<PropsWithChildren<SendToTimelineButtonProp
|
|||
: ACTION_CANNOT_INVESTIGATE_IN_TIMELINE;
|
||||
const isDisabled = !isTimelineBottomBarVisible;
|
||||
|
||||
if (dataProviders?.[0]?.queryType === 'esql' || dataProviders?.[0]?.queryType === 'sql') {
|
||||
if (!hasAccessToTimeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
*/
|
||||
|
||||
import { ATTACK_DISCOVERY_FEATURE_ID } from '../../common/constants';
|
||||
import { SERVER_APP_ID } from '../../common';
|
||||
import { SECURITY_FEATURE_ID } from '../../common';
|
||||
import { links } from './links';
|
||||
|
||||
describe('links', () => {
|
||||
it('for serverless, it specifies capabilities as an AND condition, via a nested array', () => {
|
||||
expect(links.capabilities).toEqual<string[][]>([
|
||||
[`${SERVER_APP_ID}.show`, `${ATTACK_DISCOVERY_FEATURE_ID}.attack-discovery`],
|
||||
[`${SECURITY_FEATURE_ID}.show`, `${ATTACK_DISCOVERY_FEATURE_ID}.attack-discovery`],
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -12,12 +12,14 @@ import {
|
|||
ATTACK_DISCOVERY_FEATURE_ID,
|
||||
ATTACK_DISCOVERY_PATH,
|
||||
SecurityPageName,
|
||||
SERVER_APP_ID,
|
||||
SECURITY_FEATURE_ID,
|
||||
} from '../../common/constants';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
|
||||
export const links: LinkItem = {
|
||||
capabilities: [[`${SERVER_APP_ID}.show`, `${ATTACK_DISCOVERY_FEATURE_ID}.attack-discovery`]], // This is an AND condition via the nested array
|
||||
capabilities: [
|
||||
[`${SECURITY_FEATURE_ID}.show`, `${ATTACK_DISCOVERY_FEATURE_ID}.attack-discovery`],
|
||||
], // This is an AND condition via the nested array
|
||||
globalNavPosition: 4,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.attackDiscovery', {
|
||||
|
|
|
@ -66,7 +66,7 @@ jest.mock(
|
|||
|
||||
jest.mock('../../common/links', () => ({
|
||||
useLinkInfo: jest.fn().mockReturnValue({
|
||||
capabilities: ['siem.show'],
|
||||
capabilities: ['siemV2.show'],
|
||||
globalNavPosition: 4,
|
||||
globalSearchKeywords: ['Attack discovery'],
|
||||
id: 'attack_discovery',
|
||||
|
@ -117,7 +117,7 @@ jest.mock('../../common/lib/kibana', () => {
|
|||
services: {
|
||||
application: {
|
||||
capabilities: {
|
||||
siem: { crud_alerts: true, read_alerts: true },
|
||||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
navigateToUrl: jest.fn(),
|
||||
},
|
||||
|
@ -149,7 +149,7 @@ jest.mock('../../common/lib/kibana', () => {
|
|||
dataViews: mockDataViewsService,
|
||||
docLinks: {
|
||||
links: {
|
||||
siem: {
|
||||
siemV2: {
|
||||
privileges: 'link',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,6 +21,7 @@ import { SecuritySolutionPageWrapper } from '../../common/components/page_wrappe
|
|||
import { getEndpointDetailsPath } from '../../management/common/routing';
|
||||
import { SpyRoute } from '../../common/utils/route/spy_routes';
|
||||
import { useInsertTimeline } from '../components/use_insert_timeline';
|
||||
import { useUserPrivileges } from '../../common/components/user_privileges';
|
||||
import * as timelineMarkdownPlugin from '../../common/components/markdown_editor/plugins/timeline';
|
||||
import { useFetchAlertData } from './use_fetch_alert_data';
|
||||
import { useUpsellingMessage } from '../../common/hooks/use_upselling';
|
||||
|
@ -33,6 +34,9 @@ const CaseContainerComponent: React.FC = () => {
|
|||
const userCasesPermissions = cases.helpers.canUseCases([APP_ID]);
|
||||
const dispatch = useDispatch();
|
||||
const { openFlyout } = useExpandableFlyoutApi();
|
||||
const {
|
||||
timelinePrivileges: { read: canSeeTimeline },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||
|
||||
|
@ -129,7 +133,10 @@ const CaseContainerComponent: React.FC = () => {
|
|||
editor_plugins: {
|
||||
parsingPlugin: timelineMarkdownPlugin.parser,
|
||||
processingPluginRenderer: timelineMarkdownPlugin.renderer,
|
||||
uiPlugin: timelineMarkdownPlugin.plugin({ interactionsUpsellingMessage }),
|
||||
uiPlugin: timelineMarkdownPlugin.plugin({
|
||||
interactionsUpsellingMessage,
|
||||
canSeeTimeline,
|
||||
}),
|
||||
},
|
||||
hooks: {
|
||||
useInsertTimeline,
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
import { getSecuritySolutionLink } from '@kbn/cloud-defend-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { SecurityPageName } from '../../common/constants';
|
||||
import { SERVER_APP_ID } from '../../common/constants';
|
||||
import { SECURITY_FEATURE_ID } from '../../common/constants';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
import { IconCloudDefend } from '../common/icons/cloud_defend';
|
||||
|
||||
const commonLinkProperties: Partial<LinkItem> = {
|
||||
hideTimeline: true,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
};
|
||||
|
||||
export const cloudDefendLink: LinkItem = {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { getSecuritySolutionLink } from '@kbn/cloud-security-posture-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { SecurityPageName } from '../../common/constants';
|
||||
import { SERVER_APP_ID } from '../../common/constants';
|
||||
import { SECURITY_FEATURE_ID } from '../../common/constants';
|
||||
import cloudSecurityPostureDashboardImage from '../common/images/cloud_security_posture_dashboard_page.png';
|
||||
import cloudNativeVulnerabilityManagementDashboardImage from '../common/images/cloud_native_vulnerability_management_dashboard_page.png';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
|
@ -15,7 +15,7 @@ import { IconEndpoints } from '../common/icons/endpoints';
|
|||
|
||||
const commonLinkProperties: Partial<LinkItem> = {
|
||||
hideTimeline: true,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
};
|
||||
|
||||
export const findingsLinks: LinkItem = {
|
||||
|
|
|
@ -19,6 +19,8 @@ import { DocumentDetailsRightPanelKey } from '../../../../flyout/document_detail
|
|||
import type { ExpandableFlyoutState } from '@kbn/expandable-flyout';
|
||||
import { useExpandableFlyoutApi, useExpandableFlyoutState } from '@kbn/expandable-flyout';
|
||||
import { createExpandableFlyoutApiMock } from '../../../mock/expandable_flyout';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
import { initialUserPrivilegesState } from '../../user_privileges/user_privileges_context';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('react-redux', () => {
|
||||
|
@ -57,6 +59,8 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
|
|||
});
|
||||
jest.mock('../../guided_onboarding_tour/tour_step');
|
||||
|
||||
jest.mock('../../user_privileges');
|
||||
|
||||
const mockRouteSpy: RouteSpyState = {
|
||||
pageName: SecurityPageName.overview,
|
||||
detailName: undefined,
|
||||
|
@ -140,4 +144,40 @@ describe('RowAction', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('privileges', () => {
|
||||
test('should show notes and timeline buttons when the user has the required privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
...initialUserPrivilegesState(),
|
||||
notesPrivileges: { read: true },
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<RowAction {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.queryByTestId('timeline-notes-button-small')).toBeInTheDocument();
|
||||
expect(wrapper.queryByTestId('send-alert-to-timeline-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should not show notes and timeline buttons when the user does not have the required privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
...initialUserPrivilegesState(),
|
||||
notesPrivileges: { read: false },
|
||||
timelinePrivileges: { read: false },
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<RowAction {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.queryByTestId('timeline-notes-button-small')).not.toBeInTheDocument();
|
||||
expect(wrapper.queryByTestId('send-alert-to-timeline-button')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ import { useTourContext } from '../../guided_onboarding_tour';
|
|||
import { AlertsCasesTourSteps, SecurityStepId } from '../../guided_onboarding_tour/tour_config';
|
||||
import { NotesEventTypes, DocumentEventTypes } from '../../../lib/telemetry';
|
||||
import { getMappedNonEcsValue } from '../../../utils/get_mapped_non_ecs_value';
|
||||
import { useUserPrivileges } from '../../user_privileges';
|
||||
|
||||
export type RowActionProps = EuiDataGridCellValueElementProps & {
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
|
@ -97,6 +98,11 @@ const RowActionComponent = ({
|
|||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
const {
|
||||
notesPrivileges: { read: canReadNotes },
|
||||
timelinePrivileges: { read: canReadTimelines },
|
||||
} = useUserPrivileges();
|
||||
const showNotes = canReadNotes && !securitySolutionNotesDisabled;
|
||||
|
||||
const handleOnEventDetailPanelOpened = useCallback(() => {
|
||||
openFlyout({
|
||||
|
@ -181,7 +187,8 @@ const RowActionComponent = ({
|
|||
setEventsLoading={setEventsLoading}
|
||||
setEventsDeleted={setEventsDeleted}
|
||||
refetch={refetch}
|
||||
showNotes={!securitySolutionNotesDisabled}
|
||||
showNotes={showNotes}
|
||||
disableTimelineAction={!canReadTimelines}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -11,14 +11,19 @@ import React from 'react';
|
|||
import { InvestigateInTimelineButton } from './investigate_in_timeline_button';
|
||||
import { TestProviders } from '../../mock';
|
||||
import { getDataProvider } from './use_action_cell_data_provider';
|
||||
import { useUserPrivileges } from '../user_privileges';
|
||||
|
||||
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../detections/components/alerts_table/translations';
|
||||
|
||||
jest.mock('../../lib/kibana');
|
||||
jest.mock('../user_privileges');
|
||||
|
||||
describe('InvestigateInTimelineButton', () => {
|
||||
describe('When all props are provided', () => {
|
||||
test('it should display the add to timeline button', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
const dataProviders = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'].map(
|
||||
(ipValue) => getDataProvider('host.ip', '', ipValue)
|
||||
);
|
||||
|
@ -28,6 +33,22 @@ describe('InvestigateInTimelineButton', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('should be disabled when the user has insufficient privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: false },
|
||||
});
|
||||
const dataProviders = ['127.0.0.1', '::1', '10.1.2.3', '2001:0DB8:AC10:FE01::'].map(
|
||||
(ipValue) => getDataProvider('host.ip', '', ipValue)
|
||||
);
|
||||
render(
|
||||
<TestProviders>
|
||||
<InvestigateInTimelineButton asEmptyButton={true} dataProviders={dataProviders} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import type { Filter } from '@kbn/es-query';
|
|||
import type { TimeRange } from '../../store/inputs/model';
|
||||
import type { DataProvider } from '../../../../common/types';
|
||||
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../detections/components/alerts_table/translations';
|
||||
import { useUserPrivileges } from '../user_privileges';
|
||||
import { useInvestigateInTimeline } from '../../hooks/timeline/use_investigate_in_timeline';
|
||||
|
||||
export interface InvestigateInTimelineButtonProps {
|
||||
|
@ -37,6 +38,10 @@ export interface InvestigateInTimelineButtonProps {
|
|||
iconType?: IconType;
|
||||
children?: React.ReactNode;
|
||||
flush?: EuiButtonEmptyProps['flush'];
|
||||
/**
|
||||
* Data test subject string for testing
|
||||
*/
|
||||
['data-test-subj']?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,6 +59,8 @@ export const InvestigateInTimelineButton: FC<
|
|||
keepDataView,
|
||||
iconType,
|
||||
flush,
|
||||
isDisabled,
|
||||
'data-test-subj': dataTestSubj,
|
||||
...rest
|
||||
}) => {
|
||||
const { investigateInTimeline } = useInvestigateInTimeline();
|
||||
|
@ -65,6 +72,11 @@ export const InvestigateInTimelineButton: FC<
|
|||
keepDataView,
|
||||
});
|
||||
}, [dataProviders, filters, timeRange, keepDataView, investigateInTimeline]);
|
||||
const {
|
||||
timelinePrivileges: { read: canUseTimeline },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const disabled = !canUseTimeline || isDisabled;
|
||||
|
||||
return asEmptyButton ? (
|
||||
<EuiButtonEmpty
|
||||
|
@ -73,11 +85,19 @@ export const InvestigateInTimelineButton: FC<
|
|||
flush={flush ?? 'right'}
|
||||
size="xs"
|
||||
iconType={iconType}
|
||||
disabled={disabled}
|
||||
data-test-subj={dataTestSubj}
|
||||
>
|
||||
{children}
|
||||
</EuiButtonEmpty>
|
||||
) : (
|
||||
<EuiButton aria-label={ACTION_INVESTIGATE_IN_TIMELINE} onClick={openTimelineCallback} {...rest}>
|
||||
<EuiButton
|
||||
aria-label={ACTION_INVESTIGATE_IN_TIMELINE}
|
||||
disabled={disabled}
|
||||
onClick={openTimelineCallback}
|
||||
data-test-subj={dataTestSubj}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</EuiButton>
|
||||
);
|
||||
|
|
|
@ -17,8 +17,10 @@ import { licenseService } from '../../hooks/use_license';
|
|||
import { mockHistory } from '../../mock/router';
|
||||
import { DEFAULT_EVENTS_STACK_BY_VALUE } from './histogram_configurations';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
|
||||
import { useUserPrivileges } from '../user_privileges';
|
||||
|
||||
jest.mock('../../hooks/use_experimental_features');
|
||||
jest.mock('../user_privileges');
|
||||
|
||||
const mockGetDefaultControlColumn = jest.fn();
|
||||
jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({
|
||||
|
@ -103,6 +105,9 @@ describe('EventsQueryTabBody', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
notesPrivileges: { read: true },
|
||||
});
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
@ -216,7 +221,19 @@ describe('EventsQueryTabBody', () => {
|
|||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4);
|
||||
});
|
||||
|
||||
it('should 6 columns on Action bar for Enterprise user', () => {
|
||||
it('should have 4 columns on Action bar for non-Enterprise user and if user does not have Notes privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({ notesPrivileges: { read: false } });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<EventsQueryTabBody {...commonProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(4);
|
||||
});
|
||||
|
||||
it('should have 6 columns on Action bar for Enterprise user', () => {
|
||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
||||
licenseServiceMock.isEnterprise.mockReturnValue(true);
|
||||
|
||||
|
@ -229,7 +246,7 @@ describe('EventsQueryTabBody', () => {
|
|||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(6);
|
||||
});
|
||||
|
||||
it('should 6 columns on Action bar for Enterprise user and securitySolutionNotesDisabled is true', () => {
|
||||
it('should have 5 columns on Action bar for Enterprise user and securitySolutionNotesDisabled is true', () => {
|
||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
||||
licenseServiceMock.isEnterprise.mockReturnValue(true);
|
||||
(useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true);
|
||||
|
@ -242,4 +259,18 @@ describe('EventsQueryTabBody', () => {
|
|||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5);
|
||||
});
|
||||
|
||||
it('should have 5 columns on Action bar for Enterprise user and if user does not have Notes privileges', () => {
|
||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
||||
licenseServiceMock.isEnterprise.mockReturnValue(true);
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({ notesPrivileges: { read: false } });
|
||||
|
||||
render(
|
||||
<TestProviders>
|
||||
<EventsQueryTabBody {...commonProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(mockGetDefaultControlColumn).toHaveBeenCalledWith(5);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -46,6 +46,7 @@ import {
|
|||
} from '../../utils/global_query_string/helpers';
|
||||
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
|
||||
import { SecurityCellActionsTrigger } from '../cell_actions';
|
||||
import { useUserPrivileges } from '../user_privileges';
|
||||
|
||||
export const ALERTS_EVENTS_HISTOGRAM_ID = 'alertsOrEventsHistogramQuery';
|
||||
|
||||
|
@ -61,6 +62,15 @@ export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & {
|
|||
|
||||
const EXTERNAL_ALERTS_URL_PARAM = 'onlyExternalAlerts';
|
||||
|
||||
// we show a maximum of 6 action buttons
|
||||
// - open flyout
|
||||
// - investigate in timeline
|
||||
// - 3-dot menu for more actions
|
||||
// - add new note
|
||||
// - session view
|
||||
// - analyzer graph
|
||||
const MAX_ACTION_BUTTON_COUNT = 6;
|
||||
|
||||
const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = ({
|
||||
additionalFilters,
|
||||
deleteQuery,
|
||||
|
@ -70,17 +80,27 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
|
|||
startDate,
|
||||
tableId,
|
||||
}) => {
|
||||
let ACTION_BUTTON_COUNT = MAX_ACTION_BUTTON_COUNT;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
|
||||
const isEnterprisePlus = useLicense().isEnterprise();
|
||||
let ACTION_BUTTON_COUNT = isEnterprisePlus ? 6 : 5;
|
||||
if (!isEnterprisePlus) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
const {
|
||||
notesPrivileges: { read: canReadNotes },
|
||||
} = useUserPrivileges();
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
if (securitySolutionNotesDisabled) {
|
||||
if (!canReadNotes || securitySolutionNotesDisabled) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
const leadingControlColumns = useMemo(
|
||||
() => getDefaultControlColumn(ACTION_BUTTON_COUNT),
|
||||
[ACTION_BUTTON_COUNT]
|
||||
|
|
|
@ -72,7 +72,7 @@ jest.mock('../../lib/kibana', () => {
|
|||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: jest.fn(),
|
||||
capabilities: {
|
||||
siem: { crud_alerts: true, read_alerts: true },
|
||||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
},
|
||||
cases: mockCasesContract(),
|
||||
|
@ -600,4 +600,28 @@ describe('Actions', () => {
|
|||
expect(wrapper.find('[data-test-subj="pin-event"]').exists()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timeline action', () => {
|
||||
test('should show timeline action by default', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper.find('[data-test-subj="send-alert-to-timeline-button"]').exists()
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
test('should hide timeline action when disableTimelineAction = true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<Actions {...defaultProps} disableTimelineAction />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="send-alert-to-timeline-button"]').exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -73,6 +73,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
refetch,
|
||||
toggleShowNotes,
|
||||
disablePinAction = true,
|
||||
disableTimelineAction = false,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
@ -339,7 +340,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
</GuidedOnboardingTourStep>
|
||||
)}
|
||||
<>
|
||||
{timelineId !== TimelineId.active && (
|
||||
{!disableTimelineAction && timelineId !== TimelineId.active && (
|
||||
<InvestigateInTimelineAction
|
||||
ariaLabel={i18n.SEND_ALERT_TO_TIMELINE_FOR_ROW({ ariaRowindex, columnValues })}
|
||||
key="investigate-in-timeline"
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('AddEventNoteAction', () => {
|
|||
jest.clearAllMocks();
|
||||
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
|
||||
notesPrivileges: { crud: true, read: true },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
|
@ -135,13 +135,13 @@ describe('AddEventNoteAction', () => {
|
|||
});
|
||||
|
||||
describe('button state', () => {
|
||||
test('should disable the add note button when the user does NOT have crud privileges', () => {
|
||||
test('should disable the add note button when the user does NOT have crud privileges and no notes have been created', () => {
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: false, read: true },
|
||||
notesPrivileges: { crud: false, read: true },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
renderTestComponent();
|
||||
renderTestComponent({ notesCount: 0 });
|
||||
|
||||
expect(screen.getByTestId('timeline-notes-button-small-mock')).toHaveProperty(
|
||||
'disabled',
|
||||
|
@ -151,7 +151,7 @@ describe('AddEventNoteAction', () => {
|
|||
|
||||
test('should enable the add note button when the user has crud privileges', () => {
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
|
||||
notesPrivileges: { crud: true, read: true },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
|
|
|
@ -24,7 +24,13 @@ const NOTES_ADD_TOOLTIP = i18n.translate(
|
|||
defaultMessage: 'Add note',
|
||||
}
|
||||
);
|
||||
const NOTES_COUNT_TOOLTIP = ({ notesCount }: { notesCount: number }) =>
|
||||
const NO_NOTES_TOOLTIP = i18n.translate(
|
||||
'xpack.securitySolution.timeline.body.notes.addNoteTooltip',
|
||||
{
|
||||
defaultMessage: 'No notes available',
|
||||
}
|
||||
);
|
||||
const ADD_NOTES_COUNT_TOOLTIP = ({ notesCount }: { notesCount: number }) =>
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.timeline.body.notes.addNote.multipleNotesAvailableTooltip',
|
||||
{
|
||||
|
@ -34,6 +40,16 @@ const NOTES_COUNT_TOOLTIP = ({ notesCount }: { notesCount: number }) =>
|
|||
}
|
||||
);
|
||||
|
||||
const VIEW_NOTES_COUNT_TOOLTIP = ({ notesCount }: { notesCount: number }) =>
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.timeline.body.notes.addNote.multipleNotesAvailableTooltip',
|
||||
{
|
||||
values: { notesCount },
|
||||
defaultMessage:
|
||||
'{notesCount} {notesCount, plural, one {note} other {notes} } available. Click to view {notesCount, plural, one {it} other {them}}.',
|
||||
}
|
||||
);
|
||||
|
||||
interface AddEventNoteActionProps {
|
||||
ariaLabel?: string;
|
||||
timelineType: TimelineType;
|
||||
|
@ -52,22 +68,36 @@ const AddEventNoteActionComponent: React.FC<AddEventNoteActionProps> = ({
|
|||
eventId,
|
||||
notesCount,
|
||||
}) => {
|
||||
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
|
||||
const {
|
||||
notesPrivileges: { crud: canAddNotes, read: canViewNotes },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const NOTES_TOOLTIP = useMemo(
|
||||
() => (notesCount > 0 ? NOTES_COUNT_TOOLTIP({ notesCount }) : NOTES_ADD_TOOLTIP),
|
||||
[notesCount]
|
||||
);
|
||||
const tooltip = useMemo(() => {
|
||||
if (timelineType === TimelineTypeEnum.template) {
|
||||
return NOTES_DISABLE_TOOLTIP;
|
||||
}
|
||||
if (canAddNotes) {
|
||||
return notesCount > 0 ? ADD_NOTES_COUNT_TOOLTIP({ notesCount }) : NOTES_ADD_TOOLTIP;
|
||||
}
|
||||
if (canViewNotes) {
|
||||
return notesCount > 0 ? VIEW_NOTES_COUNT_TOOLTIP({ notesCount }) : NO_NOTES_TOOLTIP;
|
||||
}
|
||||
|
||||
// we can return an empty string for tooltip because the icon is actually no shown at all
|
||||
return '';
|
||||
}, [canAddNotes, canViewNotes, notesCount, timelineType]);
|
||||
|
||||
const disabled = useMemo(() => !canAddNotes && notesCount === 0, [canAddNotes, notesCount]);
|
||||
|
||||
return (
|
||||
<ActionIconItem>
|
||||
<NotesButton
|
||||
ariaLabel={ariaLabel}
|
||||
data-test-subj="add-note"
|
||||
isDisabled={kibanaSecuritySolutionsPrivileges.crud === false}
|
||||
isDisabled={disabled}
|
||||
timelineType={timelineType}
|
||||
toggleShowNotes={toggleShowNotes}
|
||||
toolTip={timelineType === TimelineTypeEnum.template ? NOTES_DISABLE_TOOLTIP : NOTES_TOOLTIP}
|
||||
toolTip={tooltip}
|
||||
eventId={eventId}
|
||||
notesCount={notesCount}
|
||||
/>
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('PinEventAction', () => {
|
|||
describe('isDisabled', () => {
|
||||
test('it disables the pin event button when the user does NOT have crud privileges', () => {
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: false, read: true },
|
||||
timelinePrivileges: { crud: false, read: true },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe('PinEventAction', () => {
|
|||
|
||||
test('it enables the pin event button when the user has crud privileges', () => {
|
||||
useUserPrivilegesMock.mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
|
||||
timelinePrivileges: { crud: true, read: true },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialStateMock(),
|
||||
});
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
|
|||
eventIsPinned,
|
||||
timelineType,
|
||||
}) => {
|
||||
const { kibanaSecuritySolutionsPrivileges } = useUserPrivileges();
|
||||
const { timelinePrivileges } = useUserPrivileges();
|
||||
const tooltipContent = useMemo(
|
||||
() =>
|
||||
getPinTooltip({
|
||||
|
@ -51,7 +51,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
|
|||
ariaLabel={ariaLabel}
|
||||
allowUnpinning={!eventHasNotes(noteIds)}
|
||||
data-test-subj="pin-event"
|
||||
isDisabled={kibanaSecuritySolutionsPrivileges.crud === false}
|
||||
isDisabled={timelinePrivileges.crud === false}
|
||||
isAlert={isAlert}
|
||||
onClick={onPinClicked}
|
||||
pinned={eventIsPinned}
|
||||
|
|
|
@ -22,6 +22,7 @@ import type { ContextShape } from '@elastic/eui/src/components/markdown_editor/m
|
|||
|
||||
import { uiPlugins, parsingPlugins, processingPlugins } from './plugins';
|
||||
import { useUpsellingMessage } from '../../hooks/use_upselling';
|
||||
import { useUserPrivileges } from '../user_privileges';
|
||||
|
||||
interface MarkdownEditorProps {
|
||||
onChange: (content: string) => void;
|
||||
|
@ -76,14 +77,18 @@ const MarkdownEditorComponent = forwardRef<MarkdownEditorRef, MarkdownEditorProp
|
|||
|
||||
const insightsUpsellingMessage = useUpsellingMessage('investigation_guide');
|
||||
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||
const {
|
||||
timelinePrivileges: { read: canSeeTimeline },
|
||||
} = useUserPrivileges();
|
||||
const uiPluginsWithState = useMemo(() => {
|
||||
return includePlugins
|
||||
? uiPlugins({
|
||||
insightsUpsellingMessage,
|
||||
interactionsUpsellingMessage,
|
||||
canSeeTimeline,
|
||||
})
|
||||
: undefined;
|
||||
}, [includePlugins, insightsUpsellingMessage, interactionsUpsellingMessage]);
|
||||
}, [includePlugins, canSeeTimeline, insightsUpsellingMessage, interactionsUpsellingMessage]);
|
||||
|
||||
// @ts-expect-error update types
|
||||
useImperativeHandle(ref, () => {
|
||||
|
|
|
@ -23,9 +23,11 @@ export const platinumOnlyPluginTokens = [insightMarkdownPlugin.insightPrefix];
|
|||
export const uiPlugins = ({
|
||||
insightsUpsellingMessage,
|
||||
interactionsUpsellingMessage,
|
||||
canSeeTimeline,
|
||||
}: {
|
||||
insightsUpsellingMessage?: string;
|
||||
interactionsUpsellingMessage?: string;
|
||||
canSeeTimeline: boolean;
|
||||
}) => {
|
||||
const currentPlugins = nonStatefulUiPlugins.map((plugin) => plugin.name);
|
||||
const insightPluginWithLicense = insightMarkdownPlugin.plugin({
|
||||
|
@ -33,6 +35,7 @@ export const uiPlugins = ({
|
|||
});
|
||||
const timelinePluginWithLicense = timelineMarkdownPlugin.plugin({
|
||||
interactionsUpsellingMessage,
|
||||
canSeeTimeline,
|
||||
});
|
||||
const osqueryPluginWithLicense = osqueryMarkdownPlugin.plugin({
|
||||
interactionsUpsellingMessage,
|
||||
|
|
|
@ -77,15 +77,17 @@ const TimelineEditor = memo(TimelineEditorComponent);
|
|||
|
||||
export const plugin = ({
|
||||
interactionsUpsellingMessage,
|
||||
canSeeTimeline,
|
||||
}: {
|
||||
interactionsUpsellingMessage?: string;
|
||||
canSeeTimeline: boolean;
|
||||
}): EuiMarkdownEditorUiPlugin => {
|
||||
return {
|
||||
name: ID,
|
||||
button: {
|
||||
label: interactionsUpsellingMessage ?? i18n.INSERT_TIMELINE,
|
||||
iconType: 'timeline',
|
||||
isDisabled: !!interactionsUpsellingMessage,
|
||||
isDisabled: !canSeeTimeline || !!interactionsUpsellingMessage,
|
||||
},
|
||||
helpText: (
|
||||
<EuiCodeBlock language="md" paddingSize="s" fontSize="l">
|
||||
|
|
|
@ -13,6 +13,7 @@ import { useTimelineClick } from '../../../../utils/timeline/use_timeline_click'
|
|||
import type { TimelineProps } from './types';
|
||||
import * as i18n from './translations';
|
||||
import { useAppToasts } from '../../../../hooks/use_app_toasts';
|
||||
import { useUserPrivileges } from '../../../user_privileges';
|
||||
|
||||
export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
||||
id,
|
||||
|
@ -22,6 +23,10 @@ export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
|||
const { addError } = useAppToasts();
|
||||
|
||||
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||
const {
|
||||
timelinePrivileges: { read: canReadTimelines },
|
||||
} = useUserPrivileges();
|
||||
const isDisabled = !!interactionsUpsellingMessage || !canReadTimelines;
|
||||
|
||||
const handleTimelineClick = useTimelineClick();
|
||||
|
||||
|
@ -43,7 +48,7 @@ export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
|||
<EuiToolTip content={interactionsUpsellingMessage ?? i18n.TIMELINE_ID(id ?? '')}>
|
||||
<EuiLink
|
||||
onClick={onClickTimeline}
|
||||
disabled={!!interactionsUpsellingMessage}
|
||||
disabled={isDisabled}
|
||||
data-test-subj={`markdown-timeline-link-${id}`}
|
||||
>
|
||||
{title}
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('When using useEndpointPrivileges hook', () => {
|
|||
catalogue: {},
|
||||
management: {},
|
||||
navLinks: {},
|
||||
siem: {
|
||||
siemV2: {
|
||||
crud: true,
|
||||
show: true,
|
||||
},
|
||||
|
|
|
@ -7,17 +7,21 @@
|
|||
|
||||
import React, { createContext, useEffect, useState } from 'react';
|
||||
import type { Capabilities } from '@kbn/core/types';
|
||||
import { SERVER_APP_ID } from '../../../../common/constants';
|
||||
import { SECURITY_FEATURE_ID } from '../../../../common/constants';
|
||||
import { useFetchListPrivileges } from '../../../detections/components/user_privileges/use_fetch_list_privileges';
|
||||
import { useFetchDetectionEnginePrivileges } from '../../../detections/components/user_privileges/use_fetch_detection_engine_privileges';
|
||||
import { getEndpointPrivilegesInitialState, useEndpointPrivileges } from './endpoint';
|
||||
import type { EndpointPrivileges } from '../../../../common/endpoint/types';
|
||||
import { extractTimelineCapabilities } from '../../utils/timeline_capabilities';
|
||||
import { extractNotesCapabilities } from '../../utils/notes_capabilities';
|
||||
|
||||
export interface UserPrivilegesState {
|
||||
listPrivileges: ReturnType<typeof useFetchListPrivileges>;
|
||||
detectionEnginePrivileges: ReturnType<typeof useFetchDetectionEnginePrivileges>;
|
||||
endpointPrivileges: EndpointPrivileges;
|
||||
kibanaSecuritySolutionsPrivileges: { crud: boolean; read: boolean };
|
||||
timelinePrivileges: { crud: boolean; read: boolean };
|
||||
notesPrivileges: { crud: boolean; read: boolean };
|
||||
}
|
||||
|
||||
export const initialUserPrivilegesState = (): UserPrivilegesState => ({
|
||||
|
@ -25,6 +29,8 @@ export const initialUserPrivilegesState = (): UserPrivilegesState => ({
|
|||
detectionEnginePrivileges: { loading: false, error: undefined, result: undefined },
|
||||
endpointPrivileges: getEndpointPrivilegesInitialState(),
|
||||
kibanaSecuritySolutionsPrivileges: { crud: false, read: false },
|
||||
timelinePrivileges: { crud: false, read: false },
|
||||
notesPrivileges: { crud: false, read: false },
|
||||
});
|
||||
export const UserPrivilegesContext = createContext<UserPrivilegesState>(
|
||||
initialUserPrivilegesState()
|
||||
|
@ -39,8 +45,8 @@ export const UserPrivilegesProvider = ({
|
|||
kibanaCapabilities,
|
||||
children,
|
||||
}: UserPrivilegesProviderProps) => {
|
||||
const crud: boolean = kibanaCapabilities[SERVER_APP_ID].crud === true;
|
||||
const read: boolean = kibanaCapabilities[SERVER_APP_ID].show === true;
|
||||
const crud: boolean = kibanaCapabilities[SECURITY_FEATURE_ID].crud === true;
|
||||
const read: boolean = kibanaCapabilities[SECURITY_FEATURE_ID].show === true;
|
||||
const [kibanaSecuritySolutionsPrivileges, setKibanaSecuritySolutionsPrivileges] = useState({
|
||||
crud,
|
||||
read,
|
||||
|
@ -50,6 +56,34 @@ export const UserPrivilegesProvider = ({
|
|||
const detectionEnginePrivileges = useFetchDetectionEnginePrivileges(read);
|
||||
const endpointPrivileges = useEndpointPrivileges();
|
||||
|
||||
const [timelinePrivileges, setTimelinePrivileges] = useState(
|
||||
extractTimelineCapabilities(kibanaCapabilities)
|
||||
);
|
||||
const [notesPrivileges, setNotesPrivileges] = useState(
|
||||
extractNotesCapabilities(kibanaCapabilities)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setNotesPrivileges((currPrivileges) => {
|
||||
const { read: notesRead, crud: notesCrud } = extractNotesCapabilities(kibanaCapabilities);
|
||||
if (currPrivileges.read !== notesRead || currPrivileges.crud !== notesCrud) {
|
||||
return { read: notesRead, crud: notesCrud };
|
||||
}
|
||||
return currPrivileges;
|
||||
});
|
||||
}, [kibanaCapabilities]);
|
||||
|
||||
useEffect(() => {
|
||||
setTimelinePrivileges((currPrivileges) => {
|
||||
const { read: timelineRead, crud: timelineCrud } =
|
||||
extractTimelineCapabilities(kibanaCapabilities);
|
||||
if (currPrivileges.read !== timelineRead || currPrivileges.crud !== timelineCrud) {
|
||||
return { read: timelineRead, crud: timelineCrud };
|
||||
}
|
||||
return currPrivileges;
|
||||
});
|
||||
}, [kibanaCapabilities]);
|
||||
|
||||
useEffect(() => {
|
||||
setKibanaSecuritySolutionsPrivileges((currPrivileges) => {
|
||||
if (currPrivileges.read !== read || currPrivileges.crud !== crud) {
|
||||
|
@ -66,6 +100,8 @@ export const UserPrivilegesProvider = ({
|
|||
detectionEnginePrivileges,
|
||||
endpointPrivileges,
|
||||
kibanaSecuritySolutionsPrivileges,
|
||||
timelinePrivileges,
|
||||
notesPrivileges,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -32,7 +32,7 @@ import {
|
|||
DEFAULT_RULES_TABLE_REFRESH_SETTING,
|
||||
DEFAULT_RULE_REFRESH_INTERVAL_ON,
|
||||
DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
|
||||
SERVER_APP_ID,
|
||||
SECURITY_FEATURE_ID,
|
||||
} from '../../../../common/constants';
|
||||
import type { StartServices } from '../../../types';
|
||||
import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage';
|
||||
|
@ -201,7 +201,11 @@ export const createStartServicesMock = (
|
|||
...core.application,
|
||||
capabilities: {
|
||||
...core.application.capabilities,
|
||||
[SERVER_APP_ID]: {
|
||||
[SECURITY_FEATURE_ID]: {
|
||||
crud: true,
|
||||
read: true,
|
||||
},
|
||||
securitySolutionTimeline: {
|
||||
crud: true,
|
||||
read: true,
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CASES_FEATURE_ID, SecurityPageName, SERVER_APP_ID } from '../../../common/constants';
|
||||
import { CASES_FEATURE_ID, SecurityPageName, SECURITY_FEATURE_ID } from '../../../common/constants';
|
||||
import type { Capabilities } from '@kbn/core/types';
|
||||
import { mockGlobalState, TestProviders } from '../mock';
|
||||
import type { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
|
@ -53,7 +53,7 @@ const mockExperimentalDefaults = mockGlobalState.app.enableExperimental;
|
|||
|
||||
const mockCapabilities = {
|
||||
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: true },
|
||||
[SERVER_APP_ID]: { show: true },
|
||||
[SECURITY_FEATURE_ID]: { show: true },
|
||||
} as unknown as Capabilities;
|
||||
|
||||
const fakePageId = 'fakePage';
|
||||
|
@ -115,7 +115,7 @@ describe('Security links', () => {
|
|||
id: SecurityPageName.network,
|
||||
title: 'Network',
|
||||
path: '/network',
|
||||
capabilities: [`${CASES_FEATURE_ID}.read_cases`, `${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${CASES_FEATURE_ID}.read_cases`, `${SECURITY_FEATURE_ID}.show`],
|
||||
experimentalKey: 'flagEnabled' as unknown as keyof typeof mockExperimentalDefaults,
|
||||
hideWhenExperimentalKey: 'flagDisabled' as unknown as keyof typeof mockExperimentalDefaults,
|
||||
licenseType: 'basic' as const,
|
||||
|
@ -432,7 +432,7 @@ describe('Security links', () => {
|
|||
});
|
||||
|
||||
describe('hasCapabilities', () => {
|
||||
const siemShow = 'siem.show';
|
||||
const siemShow = 'siemV2.show';
|
||||
const createCases = 'securitySolutionCasesV2.create_cases';
|
||||
const readCases = 'securitySolutionCasesV2.read_cases';
|
||||
const pushCases = 'securitySolutionCasesV2.push_cases';
|
||||
|
@ -442,18 +442,20 @@ describe('Security links', () => {
|
|||
});
|
||||
|
||||
it('returns true when the capability requested is specified as a single value', () => {
|
||||
expect(hasCapabilities(createCapabilities({ siem: { show: true } }), siemShow)).toBeTruthy();
|
||||
expect(
|
||||
hasCapabilities(createCapabilities({ siemV2: { show: true } }), siemShow)
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns true when the capability requested is a single entry in an array', () => {
|
||||
expect(
|
||||
hasCapabilities(createCapabilities({ siem: { show: true } }), [siemShow])
|
||||
hasCapabilities(createCapabilities({ siemV2: { show: true } }), [siemShow])
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it("returns true when the capability requested is a single entry in an AND'd array format", () => {
|
||||
expect(
|
||||
hasCapabilities(createCapabilities({ siem: { show: true } }), [[siemShow]])
|
||||
hasCapabilities(createCapabilities({ siemV2: { show: true } }), [[siemShow]])
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
@ -461,7 +463,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: true },
|
||||
siemV2: { show: true },
|
||||
securitySolutionCasesV2: { create_cases: false },
|
||||
}),
|
||||
[siemShow, createCases]
|
||||
|
@ -473,7 +475,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: false },
|
||||
siemV2: { show: false },
|
||||
securitySolutionCasesV2: { create_cases: true },
|
||||
}),
|
||||
[siemShow, createCases]
|
||||
|
@ -485,7 +487,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: true },
|
||||
siemV2: { show: true },
|
||||
securitySolutionCasesV2: { create_cases: false },
|
||||
}),
|
||||
[readCases, createCases]
|
||||
|
@ -497,7 +499,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: true },
|
||||
siemV2: { show: true },
|
||||
securitySolutionCasesV2: { read_cases: true, create_cases: true },
|
||||
}),
|
||||
[[readCases, createCases]]
|
||||
|
@ -509,7 +511,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: false },
|
||||
siemV2: { show: false },
|
||||
securitySolutionCasesV2: { read_cases: false, create_cases: true },
|
||||
}),
|
||||
[siemShow, [readCases, createCases]]
|
||||
|
@ -521,7 +523,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: true },
|
||||
siemV2: { show: true },
|
||||
securitySolutionCasesV2: { read_cases: false, create_cases: true },
|
||||
}),
|
||||
[siemShow, [readCases, createCases]]
|
||||
|
@ -533,7 +535,7 @@ describe('Security links', () => {
|
|||
expect(
|
||||
hasCapabilities(
|
||||
createCapabilities({
|
||||
siem: { show: true },
|
||||
siemV2: { show: true },
|
||||
securitySolutionCasesV2: { read_cases: false, create_cases: true, push_cases: false },
|
||||
}),
|
||||
[
|
||||
|
|
|
@ -12,12 +12,11 @@ import { createStore } from '../store';
|
|||
import { mockGlobalState } from './global_state';
|
||||
import type { AppAction } from '../store/actions';
|
||||
import type { Immutable } from '../../../common/endpoint/types';
|
||||
import type { StartServices } from '../../types';
|
||||
import { createSecuritySolutionStorageMock } from './mock_local_storage';
|
||||
import { createStartServicesMock } from '../lib/kibana/kibana_react.mock';
|
||||
|
||||
const { storage: storageMock } = createSecuritySolutionStorageMock();
|
||||
|
||||
const kibanaMock = {} as unknown as StartServices;
|
||||
const kibanaMock = createStartServicesMock();
|
||||
|
||||
export const createMockStore = (
|
||||
state: State = mockGlobalState,
|
||||
|
|
|
@ -137,7 +137,7 @@ const TestProvidersWithPrivilegesComponent: React.FC<Props> = ({
|
|||
<UserPrivilegesProvider
|
||||
kibanaCapabilities={
|
||||
{
|
||||
siem: { show: true, crud: true },
|
||||
siemV2: { show: true, crud: true },
|
||||
[CASES_FEATURE_ID]: { read_cases: true, crud_cases: false },
|
||||
[ASSISTANT_FEATURE_ID]: { 'ai-assistant': true },
|
||||
} as unknown as Capabilities
|
||||
|
|
|
@ -29,7 +29,6 @@ import {
|
|||
DEFAULT_DATA_VIEW_ID,
|
||||
DEFAULT_INDEX_KEY,
|
||||
DETECTION_ENGINE_INDEX_URL,
|
||||
SERVER_APP_ID,
|
||||
} from '../../../common/constants';
|
||||
import { telemetryMiddleware } from '../lib/telemetry';
|
||||
import * as timelineActions from '../../timelines/store/actions';
|
||||
|
@ -56,6 +55,7 @@ import { sourcererActions } from '../../sourcerer/store';
|
|||
import { createMiddlewares } from './middlewares';
|
||||
import { addNewTimeline } from '../../timelines/store/helpers';
|
||||
import { initialNotesState } from '../../notes/store/notes.slice';
|
||||
import { hasAccessToSecuritySolution } from '../../helpers_access';
|
||||
|
||||
let store: Store<State, Action> | null = null;
|
||||
|
||||
|
@ -71,7 +71,7 @@ export const createStoreFactory = async (
|
|||
index_mapping_outdated: null,
|
||||
};
|
||||
try {
|
||||
if (coreStart.application.capabilities[SERVER_APP_ID].show === true) {
|
||||
if (hasAccessToSecuritySolution(coreStart.application.capabilities)) {
|
||||
signal = await coreStart.http.fetch(DETECTION_ENGINE_INDEX_URL, {
|
||||
version: '2023-10-31',
|
||||
method: 'GET',
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 type { Capabilities } from '@kbn/core/types';
|
||||
|
||||
export function extractNotesCapabilities(capabilities: Capabilities) {
|
||||
const notesCrud = capabilities.securitySolutionNotes?.crud === true;
|
||||
const notesRead = capabilities.securitySolutionNotes?.read === true;
|
||||
return { read: notesRead, crud: notesCrud };
|
||||
}
|
|
@ -10,9 +10,12 @@ import { allowedExperimentalValues } from '../../../../common/experimental_featu
|
|||
import { UpsellingService } from '@kbn/security-solution-upselling/service';
|
||||
import { updateAppLinks } from '../../links';
|
||||
import { appLinks } from '../../../app_links';
|
||||
import { useUserPrivileges } from '../../components/user_privileges';
|
||||
import { useShowTimeline } from './use_show_timeline';
|
||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
||||
|
||||
jest.mock('../../components/user_privileges');
|
||||
|
||||
const mockUseLocation = jest.fn().mockReturnValue({ pathname: '/overview' });
|
||||
jest.mock('react-router-dom', () => {
|
||||
const original = jest.requireActual('react-router-dom');
|
||||
|
@ -43,7 +46,7 @@ jest.mock('../../lib/kibana', () => {
|
|||
...original.useKibana().services,
|
||||
application: {
|
||||
capabilities: {
|
||||
siem: {
|
||||
siemV2: {
|
||||
show: mockSiemUserCanRead(),
|
||||
},
|
||||
},
|
||||
|
@ -58,6 +61,10 @@ const mockUiSettingsClient = uiSettingsServiceMock.createStartContract();
|
|||
|
||||
describe('use show timeline', () => {
|
||||
beforeAll(() => {
|
||||
(useUserPrivileges as unknown as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
|
||||
// initialize all App links before running test
|
||||
updateAppLinks(appLinks, {
|
||||
experimentalFeatures: allowedExperimentalValues,
|
||||
|
@ -66,7 +73,7 @@ describe('use show timeline', () => {
|
|||
management: {},
|
||||
catalogue: {},
|
||||
actions: { show: true, crud: true },
|
||||
siem: {
|
||||
siemV2: {
|
||||
show: true,
|
||||
crud: true,
|
||||
},
|
||||
|
@ -98,6 +105,24 @@ describe('use show timeline', () => {
|
|||
const { result } = renderHook(() => useShowTimeline());
|
||||
await waitFor(() => expect(result.current).toEqual([false]));
|
||||
});
|
||||
it('hides timeline for users without timeline access', async () => {
|
||||
(useUserPrivileges as unknown as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: false },
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([false]);
|
||||
});
|
||||
});
|
||||
it('shows timeline for users with timeline read access', async () => {
|
||||
(useUserPrivileges as unknown as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useShowTimeline());
|
||||
const showTimeline = result.current;
|
||||
expect(showTimeline).toEqual([true]);
|
||||
});
|
||||
|
||||
describe('sourcererDataView', () => {
|
||||
|
|
|
@ -8,14 +8,18 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useShowTimelineForGivenPath } from './use_show_timeline_for_path';
|
||||
import { useUserPrivileges } from '../../components/user_privileges';
|
||||
|
||||
export const useShowTimeline = () => {
|
||||
const { pathname } = useLocation();
|
||||
const getIsTimelineVisible = useShowTimelineForGivenPath();
|
||||
const {
|
||||
timelinePrivileges: { read: canSeeTimeline },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const showTimeline = useMemo(
|
||||
() => getIsTimelineVisible(pathname),
|
||||
[pathname, getIsTimelineVisible]
|
||||
() => canSeeTimeline && getIsTimelineVisible(pathname),
|
||||
[pathname, canSeeTimeline, getIsTimelineVisible]
|
||||
);
|
||||
return [showTimeline];
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ import { getLinksWithHiddenTimeline } from '../../links';
|
|||
import { SourcererScopeName } from '../../../sourcerer/store/model';
|
||||
import { useSourcererDataView } from '../../../sourcerer/containers';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { hasAccessToSecuritySolution } from '../../../helpers_access';
|
||||
|
||||
const isTimelinePathVisible = (currentPath: string): boolean => {
|
||||
const groupLinksWithHiddenTimelinePaths = getLinksWithHiddenTimeline().map((l) => l.path);
|
||||
|
@ -21,7 +22,12 @@ const isTimelinePathVisible = (currentPath: string): boolean => {
|
|||
|
||||
export const useShowTimelineForGivenPath = () => {
|
||||
const { indicesExist, dataViewId } = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const userHasSecuritySolutionVisible = useKibana().services.application.capabilities.siem.show;
|
||||
const {
|
||||
services: {
|
||||
application: { capabilities },
|
||||
},
|
||||
} = useKibana();
|
||||
const userHasSecuritySolutionVisible = hasAccessToSecuritySolution(capabilities);
|
||||
|
||||
const isTimelineAllowed = useMemo(
|
||||
() => userHasSecuritySolutionVisible && (indicesExist || dataViewId === null),
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 type { Capabilities } from '@kbn/core/types';
|
||||
|
||||
export function extractTimelineCapabilities(capabilities: Capabilities) {
|
||||
const timelineCrud = capabilities.securitySolutionTimeline?.crud === true;
|
||||
const timelineRead = capabilities.securitySolutionTimeline?.read === true;
|
||||
return { read: timelineRead, crud: timelineCrud };
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DASHBOARDS_PATH, SecurityPageName, SERVER_APP_ID } from '../../common/constants';
|
||||
import { DASHBOARDS_PATH, SecurityPageName, SECURITY_FEATURE_ID } from '../../common/constants';
|
||||
import { DASHBOARDS } from '../app/translations';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
import { links as kubernetesLinks } from '../kubernetes/links';
|
||||
|
@ -33,7 +33,7 @@ export const dashboardsLinks: LinkItem = {
|
|||
title: DASHBOARDS,
|
||||
path: DASHBOARDS_PATH,
|
||||
globalNavPosition: 1,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.dashboards', {
|
||||
defaultMessage: 'Dashboards',
|
||||
|
|
|
@ -28,7 +28,7 @@ jest.mock('../../../../common/lib/kibana', () => ({
|
|||
application: {
|
||||
getUrlForApp: jest.fn(),
|
||||
capabilities: {
|
||||
siem: {
|
||||
siemV2: {
|
||||
crud: true,
|
||||
},
|
||||
actions: {
|
||||
|
|
|
@ -45,6 +45,7 @@ import {
|
|||
} from '../../../rule_creation/components/related_integrations/test_helpers';
|
||||
import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability';
|
||||
import { useMLRuleConfig } from '../../../../common/components/ml/hooks/use_ml_rule_config';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
// Set the extended default timeout for all define rule step form test
|
||||
jest.setTimeout(10 * 1000);
|
||||
|
@ -207,6 +208,7 @@ jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_fro
|
|||
|
||||
jest.mock('../../../../common/hooks/esql/use_esql_availability');
|
||||
jest.mock('../../../../common/components/ml/hooks/use_ml_rule_config');
|
||||
jest.mock('../../../../common/components/user_privileges');
|
||||
|
||||
const mockUseRuleFromTimeline = useRuleFromTimeline as jest.Mock;
|
||||
const onOpenTimeline = jest.fn();
|
||||
|
@ -226,6 +228,9 @@ describe('StepDefineRule', () => {
|
|||
loading: false,
|
||||
mlSuppressionFields: [],
|
||||
});
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
|
|
|
@ -93,6 +93,7 @@ import { usePersistentNewTermsState } from './use_persistent_new_terms_state';
|
|||
import { usePersistentAlertSuppressionState } from './use_persistent_alert_suppression_state';
|
||||
import { usePersistentThresholdState } from './use_persistent_threshold_state';
|
||||
import { usePersistentQuery } from './use_persistent_query';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import { usePersistentMachineLearningState } from './use_persistent_machine_learning_state';
|
||||
import { usePersistentThreatMatchState } from './use_persistent_threat_match_state';
|
||||
|
||||
|
@ -191,6 +192,9 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
const isThresholdRule = getIsThresholdRule(ruleType);
|
||||
const alertSuppressionUpsellingMessage = useUpsellingMessage('alert_suppression_rule_form');
|
||||
const { getFields, reset, setFieldValue } = form;
|
||||
const {
|
||||
timelinePrivileges: { read: canAttachTimelineTemplates },
|
||||
} = useUserPrivileges();
|
||||
|
||||
// Callback for when user toggles between Data Views and Index Patterns
|
||||
const onChangeDataSource = useCallback(
|
||||
|
@ -700,7 +704,7 @@ const StepDefineRuleComponent: FC<StepDefineRuleProps> = ({
|
|||
component={PickTimeline}
|
||||
componentProps={{
|
||||
idAria: 'detectionEngineStepDefineRuleTimeline',
|
||||
isDisabled: isLoading,
|
||||
isDisabled: isLoading || !canAttachTimelineTemplates,
|
||||
dataTestSubj: 'detectionEngineStepDefineRuleTimeline',
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -15,6 +15,7 @@ import React, { useCallback } from 'react';
|
|||
import { MAX_MANUAL_RULE_RUN_BULK_SIZE } from '../../../../../../common/constants';
|
||||
import type { TimeRange } from '../../../../rule_gaps/types';
|
||||
import { useKibana } from '../../../../../common/lib/kibana';
|
||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { convertRulesFilterToKQL } from '../../../../../../common/detection_engine/rule_management/rule_filtering';
|
||||
import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants';
|
||||
import type {
|
||||
|
@ -83,6 +84,9 @@ export const useBulkActions = ({
|
|||
const { executeBulkAction } = useExecuteBulkAction();
|
||||
const { bulkExport } = useBulkExport();
|
||||
const downloadExportedRules = useDownloadExportedRules();
|
||||
const {
|
||||
timelinePrivileges: { crud: canCreateTimelines },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const {
|
||||
state: { isAllSelected, rules, loadingRuleIds, selectedRuleIds },
|
||||
|
@ -431,7 +435,7 @@ export const useBulkActions = ({
|
|||
key: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE,
|
||||
name: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE,
|
||||
'data-test-subj': 'applyTimelineTemplateBulk',
|
||||
disabled: isEditDisabled,
|
||||
disabled: !canCreateTimelines || isEditDisabled,
|
||||
onClick: handleBulkEdit(BulkActionEditTypeEnum.set_timeline),
|
||||
toolTipContent: missingActionPrivileges
|
||||
? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES
|
||||
|
@ -595,6 +599,7 @@ export const useBulkActions = ({
|
|||
filterOptions,
|
||||
completeBulkEditForm,
|
||||
startServices,
|
||||
canCreateTimelines,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
services: {
|
||||
timelines: { ...mockTimelines },
|
||||
application: {
|
||||
capabilities: { siem: { crud_alerts: true, read_alerts: true } },
|
||||
capabilities: { siemV2: { crud_alerts: true, read_alerts: true } },
|
||||
},
|
||||
cases: {
|
||||
...mockCasesContract(),
|
||||
|
|
|
@ -13,6 +13,7 @@ import * as actions from '../actions';
|
|||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { InvestigateInTimelineAction } from './investigate_in_timeline_action';
|
||||
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
const ecsRowData: Ecs = {
|
||||
_id: '1',
|
||||
|
@ -28,6 +29,7 @@ const ecsRowData: Ecs = {
|
|||
},
|
||||
};
|
||||
|
||||
jest.mock('../../../../common/components/user_privileges');
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
jest.mock('../../../../common/lib/apm/use_start_transaction');
|
||||
jest.mock('../../../../common/hooks/use_app_toasts');
|
||||
|
@ -48,6 +50,12 @@ const mockSendAlertToTimeline = jest.spyOn(actions, 'sendAlertToTimelineAction')
|
|||
(useAppToasts as jest.Mock).mockReturnValue({
|
||||
addError: jest.fn(),
|
||||
});
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: {
|
||||
crud: true,
|
||||
read: true,
|
||||
},
|
||||
});
|
||||
|
||||
const props = {
|
||||
ecsRowData,
|
||||
|
@ -79,4 +87,18 @@ describe('use investigate in timeline hook', () => {
|
|||
});
|
||||
expect(mockSendAlertToTimeline).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
test('it disables the button when the user does not have access to timeline', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: {
|
||||
read: false,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<InvestigateInTimelineAction {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.getByTestId('send-alert-to-timeline-button')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { ActionIconItem } from '../../../../common/components/header_actions/action_icon_item';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
import {
|
||||
ACTION_INVESTIGATE_IN_TIMELINE,
|
||||
|
@ -32,6 +33,10 @@ const InvestigateInTimelineActionComponent: React.FC<InvestigateInTimelineAction
|
|||
ecsRowData,
|
||||
onInvestigateInTimelineAlertClick,
|
||||
});
|
||||
const {
|
||||
timelinePrivileges: { read },
|
||||
} = useUserPrivileges();
|
||||
const cannotReadTimeline = !read;
|
||||
|
||||
return (
|
||||
<ActionIconItem
|
||||
|
@ -40,7 +45,7 @@ const InvestigateInTimelineActionComponent: React.FC<InvestigateInTimelineAction
|
|||
dataTestSubj="send-alert-to-timeline"
|
||||
iconType="timeline"
|
||||
onClick={investigateInTimelineAlertClick}
|
||||
isDisabled={false}
|
||||
isDisabled={cannotReadTimeline}
|
||||
buttonType={buttonType}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -19,11 +19,13 @@ import { EuiPopover, EuiContextMenu } from '@elastic/eui';
|
|||
import * as timelineActions from '../../../../timelines/store/actions';
|
||||
import { getTimelineTemplate } from '../../../../timelines/containers/api';
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
jest.mock('../../../../timelines/containers/api');
|
||||
jest.mock('../../../../common/lib/apm/use_start_transaction');
|
||||
jest.mock('../../../../common/hooks/use_app_toasts');
|
||||
jest.mock('../../../../common/components/user_privileges');
|
||||
|
||||
const ecsRowData: Ecs = {
|
||||
_id: '1',
|
||||
|
@ -273,6 +275,9 @@ describe('useInvestigateInTimeline', () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -396,4 +401,17 @@ describe('useInvestigateInTimeline', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('privileges', () => {
|
||||
test('should not return a timeline action when the user does not have sufficient privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: false },
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useInvestigateInTimeline(props), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
expect(result.current.investigateInTimelineActionItems).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
|
|||
import { useStartTransaction } from '../../../../common/lib/apm/use_start_transaction';
|
||||
import { ALERTS_ACTIONS } from '../../../../common/lib/apm/user_actions';
|
||||
import { defaultUdtHeaders } from '../../../../timelines/components/timeline/body/column_headers/default_headers';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
interface UseInvestigateInTimelineActionProps {
|
||||
ecsRowData?: Ecs | Ecs[] | null;
|
||||
|
@ -189,17 +190,24 @@ export const useInvestigateInTimeline = ({
|
|||
getExceptionFilter,
|
||||
]);
|
||||
|
||||
const {
|
||||
timelinePrivileges: { read: canInvestigateInTimeline },
|
||||
} = useUserPrivileges();
|
||||
|
||||
const investigateInTimelineActionItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'investigate-in-timeline-action-item',
|
||||
'data-test-subj': 'investigate-in-timeline-action-item',
|
||||
disabled: ecsRowData == null,
|
||||
onClick: investigateInTimelineAlertClick,
|
||||
name: ACTION_INVESTIGATE_IN_TIMELINE,
|
||||
},
|
||||
],
|
||||
[ecsRowData, investigateInTimelineAlertClick]
|
||||
() =>
|
||||
canInvestigateInTimeline
|
||||
? [
|
||||
{
|
||||
key: 'investigate-in-timeline-action-item',
|
||||
'data-test-subj': 'investigate-in-timeline-action-item',
|
||||
disabled: ecsRowData == null,
|
||||
onClick: investigateInTimelineAlertClick,
|
||||
name: ACTION_INVESTIGATE_IN_TIMELINE,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[ecsRowData, investigateInTimelineAlertClick, canInvestigateInTimeline]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('useUserInfo', () => {
|
|||
services: {
|
||||
application: {
|
||||
capabilities: {
|
||||
siem: {
|
||||
siemV2: {
|
||||
crud: true,
|
||||
},
|
||||
},
|
||||
|
@ -68,7 +68,7 @@ describe('useUserInfo', () => {
|
|||
const wrapper = ({ children }: React.PropsWithChildren) => (
|
||||
<TestProviders>
|
||||
<UserPrivilegesProvider
|
||||
kibanaCapabilities={{ siem: { show: true, crud: true } } as unknown as Capabilities}
|
||||
kibanaCapabilities={{ siemV2: { show: true, crud: true } } as unknown as Capabilities}
|
||||
>
|
||||
<ManageUserInfo>{children}</ManageUserInfo>
|
||||
</UserPrivilegesProvider>
|
||||
|
|
|
@ -76,6 +76,8 @@ const userPrivilegesInitial: ReturnType<typeof useUserPrivileges> = {
|
|||
canAccessFleet: false,
|
||||
}),
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true, read: true },
|
||||
timelinePrivileges: { crud: true, read: true },
|
||||
notesPrivileges: { crud: true, read: true },
|
||||
};
|
||||
|
||||
describe('useAlertsPrivileges', () => {
|
||||
|
|
|
@ -24,6 +24,7 @@ import type { TimelineItem } from '../../../../common/search_strategy';
|
|||
import { getAlertsDefaultModel } from '../../components/alerts_table/default_config';
|
||||
import type { State } from '../../../common/store';
|
||||
import { RowAction } from '../../../common/components/control_columns/row_action';
|
||||
import { useUserPrivileges } from '../../../common/components/user_privileges';
|
||||
|
||||
// we show a maximum of 6 action buttons
|
||||
// - open flyout
|
||||
|
@ -46,11 +47,21 @@ export const getUseActionColumnHook =
|
|||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
// we only want to show the note icon if the new notes system feature flag is enabled
|
||||
const {
|
||||
timelinePrivileges: { read: canReadTimelines },
|
||||
notesPrivileges: { read: canReadNotes },
|
||||
} = useUserPrivileges();
|
||||
|
||||
// remove space if investigate timeline icon shouldn't be displayed
|
||||
if (!canReadTimelines) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
// remove space if add notes icon shouldn't be displayed
|
||||
const securitySolutionNotesDisabled = useIsExperimentalFeatureEnabled(
|
||||
'securitySolutionNotesDisabled'
|
||||
);
|
||||
if (securitySolutionNotesDisabled) {
|
||||
if (!canReadNotes || securitySolutionNotesDisabled) {
|
||||
ACTION_BUTTON_COUNT--;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALERTS_PATH, SecurityPageName, SERVER_APP_ID } from '../../common/constants';
|
||||
import { ALERTS_PATH, SecurityPageName, SECURITY_FEATURE_ID } from '../../common/constants';
|
||||
import { ALERTS } from '../app/translations';
|
||||
import type { LinkItem } from '../common/links/types';
|
||||
|
||||
|
@ -13,7 +13,7 @@ export const links: LinkItem = {
|
|||
id: SecurityPageName.alerts,
|
||||
title: ALERTS,
|
||||
path: ALERTS_PATH,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
globalNavPosition: 3,
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.alerts', {
|
||||
|
|
|
@ -104,7 +104,7 @@ jest.mock('../../../common/lib/kibana', () => {
|
|||
application: {
|
||||
navigateToUrl: jest.fn(),
|
||||
capabilities: {
|
||||
siem: { crud_alerts: true, read_alerts: true },
|
||||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
},
|
||||
dataViews: mockDataViewsService,
|
||||
|
@ -119,7 +119,7 @@ jest.mock('../../../common/lib/kibana', () => {
|
|||
},
|
||||
docLinks: {
|
||||
links: {
|
||||
siem: {
|
||||
siemV2: {
|
||||
privileges: 'link',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -13,6 +13,7 @@ import React from 'react';
|
|||
import { TestProviders } from '../../../../common/mock';
|
||||
import { alertInputDataMock } from '../mocks';
|
||||
import { useRiskInputActionsPanels } from './use_risk_input_actions_panels';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
const casesServiceMock = casesPluginMock.createStartContract();
|
||||
const mockCanUseCases = jest.fn().mockReturnValue({
|
||||
|
@ -42,6 +43,13 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
|
|||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../../common/components/user_privileges');
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: {
|
||||
read: false,
|
||||
},
|
||||
});
|
||||
|
||||
const TestMenu = ({ panels }: { panels: EuiContextMenuPanelDescriptor[] }) => (
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
);
|
||||
|
@ -89,4 +97,22 @@ describe('useRiskInputActionsPanels', () => {
|
|||
expect(container).not.toHaveTextContent('Add to existing case');
|
||||
expect(container).not.toHaveTextContent('Add to new case');
|
||||
});
|
||||
|
||||
it('displays the timeline action when user has sufficient privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: true },
|
||||
});
|
||||
const { container } = customRender();
|
||||
|
||||
expect(container).toHaveTextContent('Add to new timeline');
|
||||
});
|
||||
|
||||
it('does NOT display the timeline action when user has NO insufficient privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
timelinePrivileges: { read: false },
|
||||
});
|
||||
const { container } = customRender();
|
||||
|
||||
expect(container).not.toHaveTextContent('Add to new timeline');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ import { get } from 'lodash/fp';
|
|||
import { ALERT_RULE_NAME } from '@kbn/rule-data-utils';
|
||||
import { useRiskInputActions } from './use_risk_input_actions';
|
||||
import type { InputAlert } from '../../../hooks/use_risk_contributing_alerts';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
export const useRiskInputActionsPanels = (inputs: InputAlert[], closePopover: () => void) => {
|
||||
const { cases: casesService } = useKibana<{ cases?: CasesService }>().services;
|
||||
|
@ -25,6 +26,9 @@ export const useRiskInputActionsPanels = (inputs: InputAlert[], closePopover: ()
|
|||
);
|
||||
const userCasesPermissions = casesService?.helpers.canUseCases([SECURITY_SOLUTION_OWNER]);
|
||||
const hasCasesPermissions = userCasesPermissions?.create && userCasesPermissions?.read;
|
||||
const {
|
||||
timelinePrivileges: { read: canAddToTimeline },
|
||||
} = useUserPrivileges();
|
||||
|
||||
return useMemo(() => {
|
||||
const timelinePanel = {
|
||||
|
@ -68,33 +72,42 @@ export const useRiskInputActionsPanels = (inputs: InputAlert[], closePopover: ()
|
|||
/>
|
||||
),
|
||||
id: 0,
|
||||
items: hasCasesPermissions
|
||||
? [
|
||||
timelinePanel,
|
||||
{
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.entityDetails.riskInputs.actions.addToNewCase"
|
||||
defaultMessage="Add to new case"
|
||||
/>
|
||||
),
|
||||
items: [
|
||||
...(canAddToTimeline ? [timelinePanel] : []),
|
||||
...(hasCasesPermissions
|
||||
? [
|
||||
{
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.entityDetails.riskInputs.actions.addToNewCase"
|
||||
defaultMessage="Add to new case"
|
||||
/>
|
||||
),
|
||||
|
||||
onClick: addToNewCaseClick,
|
||||
},
|
||||
onClick: addToNewCaseClick,
|
||||
},
|
||||
|
||||
{
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.entityDetails.riskInputs.actions.addToExistingCase"
|
||||
defaultMessage="Add to existing case"
|
||||
/>
|
||||
),
|
||||
{
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.entityDetails.riskInputs.actions.addToExistingCase"
|
||||
defaultMessage="Add to existing case"
|
||||
/>
|
||||
),
|
||||
|
||||
onClick: addToExistingCase,
|
||||
},
|
||||
]
|
||||
: [timelinePanel],
|
||||
onClick: addToExistingCase,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
];
|
||||
}, [addToExistingCase, addToNewCaseClick, addToNewTimeline, inputs, hasCasesPermissions]);
|
||||
}, [
|
||||
addToExistingCase,
|
||||
addToNewCaseClick,
|
||||
addToNewTimeline,
|
||||
inputs,
|
||||
hasCasesPermissions,
|
||||
canAddToTimeline,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
NETWORK_PATH,
|
||||
USERS_PATH,
|
||||
EXPLORE_PATH,
|
||||
SERVER_APP_ID,
|
||||
SECURITY_FEATURE_ID,
|
||||
SecurityPageName,
|
||||
} from '../../common/constants';
|
||||
import { EXPLORE, HOSTS, NETWORK, USERS } from '../app/translations';
|
||||
|
@ -34,7 +34,7 @@ const networkLinks: LinkItem = {
|
|||
defaultMessage: 'Network',
|
||||
}),
|
||||
],
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.networkFlows,
|
||||
|
@ -97,7 +97,7 @@ const usersLinks: LinkItem = {
|
|||
defaultMessage: 'Users',
|
||||
}),
|
||||
],
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.usersAll,
|
||||
|
@ -152,7 +152,7 @@ const hostsLinks: LinkItem = {
|
|||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
],
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
links: [
|
||||
{
|
||||
id: SecurityPageName.hostsAll,
|
||||
|
@ -209,7 +209,7 @@ export const exploreLinks: LinkItem = {
|
|||
title: EXPLORE,
|
||||
path: EXPLORE_PATH,
|
||||
globalNavPosition: 9,
|
||||
capabilities: [`${SERVER_APP_ID}.show`],
|
||||
capabilities: [`${SECURITY_FEATURE_ID}.show`],
|
||||
globalSearchKeywords: [
|
||||
i18n.translate('xpack.securitySolution.appLinks.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
|
|
|
@ -82,7 +82,7 @@ jest.mock('../../../common/lib/kibana', () => {
|
|||
application: {
|
||||
...original.useKibana().services.application,
|
||||
capabilities: {
|
||||
siem: { crud_alerts: true, read_alerts: true },
|
||||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
maps: mockMapVisibility(),
|
||||
},
|
||||
navigateToApp: mockNavigateToApp,
|
||||
|
|
|
@ -24,7 +24,7 @@ const mockSetAttachToTimeline = jest.fn();
|
|||
describe('AttachToActiveTimeline', () => {
|
||||
it('should render the component for an unsaved timeline', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
kibanaSecuritySolutionsPrivileges: { crud: true },
|
||||
timelinePrivileges: { crud: true },
|
||||
});
|
||||
const mockStore = createMockStore({
|
||||
...mockGlobalState,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue