Skip to content

Commit 24ae532

Browse files
semdkibanamachine
andauthored
[One Workflow] Fix workflows features configuration (#241015)
## Summary Closes elastic/security-team#14403 - Uses the correct API action in the server routes from the privilege configuration - UI capabilities and API actions values extracted as enums to the package so they can be easily reused --------- Co-authored-by: kibanamachine <[email protected]>
1 parent 14a2473 commit 24ae532

33 files changed

+433
-900
lines changed

src/platform/packages/shared/kbn-workflows/common/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
/**
11+
* Feature ID for the workflows management feature
12+
*/
13+
export const WORKFLOWS_MANAGEMENT_FEATURE_ID = 'workflowsManagement';
14+
1015
/**
1116
* UI Setting ID for enabling / disabling the workflows management UI
1217
*/
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { WORKFLOWS_MANAGEMENT_FEATURE_ID } from './constants';
11+
12+
// The API actions added by feature privileges, to be checked in the API routes.
13+
// Api actions are not scoped by feature ID, so we scope it by adding the feature ID ("workflowsManagement") as prefix.
14+
// example: security.authz.requiredPrivileges: ["workflowsManagement:create"]
15+
export enum WorkflowsManagementApiActions {
16+
'create' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:create`,
17+
'read' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:read`,
18+
'update' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:update`,
19+
'delete' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:delete`,
20+
'execute' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:execute`,
21+
'readExecution' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:readExecution`,
22+
'cancelExecution' = `${WORKFLOWS_MANAGEMENT_FEATURE_ID}:cancelExecution`,
23+
}
24+
25+
// The UI actions (aka capabilities) added by feature privileges, to be checked in the UI components.
26+
// UI actions are scoped by feature ID ("workflowsManagement"), so no need to add any prefix.
27+
// example: application.capabilities.workflowsManagement.createWorkflow
28+
export enum WorkflowsManagementUiActions {
29+
'create' = 'createWorkflow',
30+
'read' = 'readWorkflow',
31+
'update' = 'updateWorkflow',
32+
'delete' = 'deleteWorkflow',
33+
'execute' = 'executeWorkflow',
34+
'readExecution' = 'readWorkflowExecution',
35+
'cancelExecution' = 'cancelWorkflowExecution',
36+
}

src/platform/packages/shared/kbn-workflows/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export * from './spec/schema';
1313
export * from './types/latest';
1414
export * from './types/utils';
1515
export * from './common/constants';
16+
export * from './common/privileges';
1617
export * from './common/elasticsearch_request_builder';
1718
export * from './common/kibana_request_builder';
1819

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { renderHook } from '@testing-library/react';
11+
import { WORKFLOWS_MANAGEMENT_FEATURE_ID } from '@kbn/workflows/common/constants';
12+
import { useCapabilities } from './use_capabilities';
13+
import { useKibana } from './use_kibana';
14+
import { createStartServicesMock } from '../mocks';
15+
16+
jest.mock('./use_kibana');
17+
const mockUseKibana = useKibana as jest.Mock;
18+
19+
describe('useCapabilities', () => {
20+
let mockServices: ReturnType<typeof createStartServicesMock>;
21+
22+
const mockCapabilities = (capabilities: any) => {
23+
mockServices.application.capabilities = {
24+
[WORKFLOWS_MANAGEMENT_FEATURE_ID]: capabilities,
25+
navLinks: {},
26+
management: {},
27+
catalogue: {},
28+
};
29+
};
30+
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
mockServices = createStartServicesMock();
34+
mockUseKibana.mockReturnValue({ services: mockServices });
35+
});
36+
37+
describe('when all capabilities are enabled', () => {
38+
beforeEach(() => {
39+
mockCapabilities({
40+
createWorkflow: true,
41+
readWorkflow: true,
42+
updateWorkflow: true,
43+
deleteWorkflow: true,
44+
executeWorkflow: true,
45+
readWorkflowExecution: true,
46+
cancelWorkflowExecution: true,
47+
});
48+
});
49+
50+
it('should return all capabilities as true', () => {
51+
const { result } = renderHook(useCapabilities);
52+
53+
expect(result.current).toEqual({
54+
canCreateWorkflow: true,
55+
canReadWorkflow: true,
56+
canUpdateWorkflow: true,
57+
canDeleteWorkflow: true,
58+
canExecuteWorkflow: true,
59+
canReadWorkflowExecution: true,
60+
canCancelWorkflowExecution: true,
61+
});
62+
});
63+
});
64+
65+
describe('when all capabilities are disabled', () => {
66+
beforeEach(() => {
67+
mockCapabilities({
68+
createWorkflow: false,
69+
readWorkflow: false,
70+
updateWorkflow: false,
71+
deleteWorkflow: false,
72+
executeWorkflow: false,
73+
readWorkflowExecution: false,
74+
cancelWorkflowExecution: false,
75+
});
76+
});
77+
78+
it('should return all capabilities as false', () => {
79+
const { result } = renderHook(useCapabilities);
80+
81+
expect(result.current).toEqual({
82+
canCreateWorkflow: false,
83+
canReadWorkflow: false,
84+
canUpdateWorkflow: false,
85+
canDeleteWorkflow: false,
86+
canExecuteWorkflow: false,
87+
canReadWorkflowExecution: false,
88+
canCancelWorkflowExecution: false,
89+
});
90+
});
91+
});
92+
93+
describe('when some capabilities are enabled', () => {
94+
beforeEach(() => {
95+
mockCapabilities({
96+
createWorkflow: true,
97+
readWorkflow: true,
98+
updateWorkflow: false,
99+
deleteWorkflow: false,
100+
executeWorkflow: true,
101+
readWorkflowExecution: false,
102+
cancelWorkflowExecution: false,
103+
});
104+
});
105+
106+
it('should return correct mixed capabilities', () => {
107+
const { result } = renderHook(useCapabilities);
108+
109+
expect(result.current).toEqual({
110+
canCreateWorkflow: true,
111+
canReadWorkflow: true,
112+
canUpdateWorkflow: false,
113+
canDeleteWorkflow: false,
114+
canExecuteWorkflow: true,
115+
canReadWorkflowExecution: false,
116+
canCancelWorkflowExecution: false,
117+
});
118+
});
119+
});
120+
121+
describe('when capabilities object is undefined', () => {
122+
beforeEach(() => {
123+
mockCapabilities(undefined);
124+
});
125+
126+
it('should return all capabilities as false', () => {
127+
const { result } = renderHook(useCapabilities);
128+
129+
expect(result.current).toEqual({
130+
canCreateWorkflow: false,
131+
canReadWorkflow: false,
132+
canUpdateWorkflow: false,
133+
canDeleteWorkflow: false,
134+
canExecuteWorkflow: false,
135+
canReadWorkflowExecution: false,
136+
canCancelWorkflowExecution: false,
137+
});
138+
});
139+
});
140+
141+
describe('when capabilities object does not exist', () => {
142+
beforeEach(() => {
143+
mockCapabilities({});
144+
});
145+
146+
it('should return all capabilities as false', () => {
147+
const { result } = renderHook(useCapabilities);
148+
149+
expect(result.current).toEqual({
150+
canCreateWorkflow: false,
151+
canReadWorkflow: false,
152+
canUpdateWorkflow: false,
153+
canDeleteWorkflow: false,
154+
canExecuteWorkflow: false,
155+
canReadWorkflowExecution: false,
156+
canCancelWorkflowExecution: false,
157+
});
158+
});
159+
});
160+
161+
describe('when application is undefined', () => {
162+
beforeEach(() => {
163+
mockServices.application = undefined as any;
164+
});
165+
166+
it('should return all capabilities as false', () => {
167+
const { result } = renderHook(useCapabilities);
168+
169+
expect(result.current).toEqual({
170+
canCreateWorkflow: false,
171+
canReadWorkflow: false,
172+
canUpdateWorkflow: false,
173+
canDeleteWorkflow: false,
174+
canExecuteWorkflow: false,
175+
canReadWorkflowExecution: false,
176+
canCancelWorkflowExecution: false,
177+
});
178+
});
179+
});
180+
181+
describe('when capability values are falsy', () => {
182+
beforeEach(() => {
183+
mockCapabilities({
184+
createWorkflow: 0,
185+
readWorkflow: '',
186+
updateWorkflow: null,
187+
deleteWorkflow: undefined,
188+
executeWorkflow: false,
189+
readWorkflowExecution: 0,
190+
cancelWorkflowExecution: null,
191+
});
192+
});
193+
194+
it('should convert all falsy values to false', () => {
195+
const { result } = renderHook(useCapabilities);
196+
197+
expect(result.current).toEqual({
198+
canCreateWorkflow: false,
199+
canReadWorkflow: false,
200+
canUpdateWorkflow: false,
201+
canDeleteWorkflow: false,
202+
canExecuteWorkflow: false,
203+
canReadWorkflowExecution: false,
204+
canCancelWorkflowExecution: false,
205+
});
206+
});
207+
});
208+
209+
describe('when capability values are truthy', () => {
210+
beforeEach(() => {
211+
mockCapabilities({
212+
createWorkflow: 1,
213+
readWorkflow: 'true',
214+
updateWorkflow: {},
215+
deleteWorkflow: [],
216+
executeWorkflow: true,
217+
readWorkflowExecution: 'test',
218+
cancelWorkflowExecution: 42,
219+
});
220+
});
221+
222+
it('should convert all truthy values to true', () => {
223+
const { result } = renderHook(useCapabilities);
224+
225+
expect(result.current).toEqual({
226+
canCreateWorkflow: true,
227+
canReadWorkflow: true,
228+
canUpdateWorkflow: true,
229+
canDeleteWorkflow: true,
230+
canExecuteWorkflow: true,
231+
canReadWorkflowExecution: true,
232+
canCancelWorkflowExecution: true,
233+
});
234+
});
235+
});
236+
});

src/platform/plugins/shared/workflows_management/public/hooks/use_capabilities.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,31 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import { WORKFLOWS_MANAGEMENT_FEATURE_ID } from '@kbn/workflows/common/constants';
11+
import { WorkflowsManagementUiActions } from '@kbn/workflows/common/privileges';
1012
import { useKibana } from './use_kibana';
1113

1214
const CapabilitiesMap = {
13-
canCreateWorkflow: 'createWorkflow',
14-
canReadWorkflow: 'readWorkflow',
15-
canUpdateWorkflow: 'updateWorkflow',
16-
canDeleteWorkflow: 'deleteWorkflow',
17-
canExecuteWorkflow: 'executeWorkflow',
18-
canReadWorkflowExecution: 'readWorkflowExecution',
19-
canCancelWorkflowExecution: 'cancelWorkflowExecution',
15+
canCreateWorkflow: WorkflowsManagementUiActions.create,
16+
canReadWorkflow: WorkflowsManagementUiActions.read,
17+
canUpdateWorkflow: WorkflowsManagementUiActions.update,
18+
canDeleteWorkflow: WorkflowsManagementUiActions.delete,
19+
canExecuteWorkflow: WorkflowsManagementUiActions.execute,
20+
canReadWorkflowExecution: WorkflowsManagementUiActions.readExecution,
21+
canCancelWorkflowExecution: WorkflowsManagementUiActions.cancelExecution,
2022
} as const;
2123

2224
export type CapabilitiesKey = keyof typeof CapabilitiesMap;
2325
export type WorkflowsManagementCapabilities = Record<CapabilitiesKey, boolean>;
2426

2527
export const useCapabilities = (): WorkflowsManagementCapabilities => {
2628
const { application } = useKibana().services;
29+
const workflowsCapabilities = application?.capabilities?.[WORKFLOWS_MANAGEMENT_FEATURE_ID] ?? {};
2730

2831
return Object.fromEntries(
2932
Object.entries(CapabilitiesMap).map(([key, value]) => [
3033
key,
31-
Boolean(application?.capabilities.workflowsManagement?.[value]),
34+
Boolean(workflowsCapabilities[value]),
3235
])
3336
) as WorkflowsManagementCapabilities;
3437
};

0 commit comments

Comments
 (0)