foreman_openscap 5.1.0 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/graphql/mutations/oval_policies/create.rb +33 -0
  3. data/app/helpers/policies_helper.rb +1 -1
  4. data/app/models/concerns/foreman_openscap/data_stream_content.rb +1 -1
  5. data/app/models/concerns/foreman_openscap/oval_facet_hostgroup_extensions.rb +1 -0
  6. data/app/models/foreman_openscap/arf_report.rb +1 -1
  7. data/app/models/foreman_openscap/oval_content.rb +1 -1
  8. data/app/services/foreman_openscap/oval/configure.rb +16 -13
  9. data/app/services/foreman_openscap/oval/setup_check.rb +1 -1
  10. data/lib/foreman_openscap/engine.rb +3 -2
  11. data/lib/foreman_openscap/version.rb +1 -1
  12. data/webpack/components/EditableInput.js +16 -10
  13. data/webpack/components/IndexTable/index.js +7 -2
  14. data/webpack/components/LinkButton.js +14 -2
  15. data/webpack/components/withLoading.js +3 -1
  16. data/webpack/graphql/mutations/createOvalPolicy.gql +22 -0
  17. data/webpack/graphql/queries/ovalPolicy.gql +3 -0
  18. data/webpack/helpers/formFieldsHelper.js +51 -1
  19. data/webpack/helpers/globalIdHelper.js +4 -2
  20. data/webpack/helpers/pathsHelper.js +5 -3
  21. data/webpack/helpers/toastsHelper.js +3 -0
  22. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +6 -1
  23. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +18 -1
  24. data/webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js +135 -0
  25. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js +119 -0
  26. data/webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js +107 -0
  27. data/webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js +32 -0
  28. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js +147 -0
  29. data/webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js +172 -0
  30. data/webpack/routes/OvalPolicies/OvalPoliciesNew/index.js +11 -0
  31. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +2 -2
  32. data/webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js +2 -0
  33. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +4 -3
  34. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesEdit.test.js +27 -0
  35. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +11 -1
  36. data/webpack/routes/routes.js +7 -0
  37. data/webpack/testHelper.js +22 -0
  38. metadata +12 -2
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+
6
+ import OvalPoliciesNew from '../';
7
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
8
+
9
+ import { unpagedMocks as ovalContentMocks } from '../../../OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures';
10
+
11
+ import {
12
+ withMockedProvider,
13
+ wait,
14
+ withRouter,
15
+ withRedux,
16
+ } from '../../../../testHelper';
17
+
18
+ import {
19
+ newPolicyName,
20
+ newPolicyDescription,
21
+ newPolicyCronline,
22
+ newPolicyContentName,
23
+ policySuccessMock,
24
+ policyValidationMock,
25
+ policyPreconditionMock,
26
+ policyInvalidHgMock,
27
+ hostgroupsMock,
28
+ firstHg,
29
+ roleAbsent as roleAbsentCheck,
30
+ hgWithoutProxy as withoutProxyCheck,
31
+ } from './OvalPoliciesNew.fixtures';
32
+
33
+ import * as toasts from '../../../../helpers/toastsHelper';
34
+
35
+ const TestComponent = withRouter(
36
+ withRedux(withMockedProvider(OvalPoliciesNew))
37
+ );
38
+
39
+ describe('OvalPoliciesNew', () => {
40
+ it('should create new OVAL policy', async () => {
41
+ const showToast = jest.fn();
42
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
43
+ const pushMock = jest.fn();
44
+
45
+ render(
46
+ <TestComponent
47
+ mocks={ovalContentMocks.concat(policySuccessMock)}
48
+ history={{
49
+ push: pushMock,
50
+ }}
51
+ />
52
+ );
53
+ expect(screen.getByText('Loading')).toBeInTheDocument();
54
+ await wait();
55
+ const submitBtn = screen.getByRole('button', { name: 'submit' });
56
+ expect(submitBtn).toBeDisabled();
57
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
58
+ await wait();
59
+ expect(submitBtn).toBeDisabled();
60
+ userEvent.type(screen.getByLabelText(/cronLine/), 'foo');
61
+ userEvent.type(screen.getByLabelText(/description/), newPolicyDescription);
62
+ userEvent.selectOptions(
63
+ screen.getByLabelText(/ovalContentId/),
64
+ newPolicyContentName
65
+ );
66
+ await wait();
67
+ expect(screen.getByText('is not a valid cronline')).toBeInTheDocument();
68
+ expect(submitBtn).toBeDisabled();
69
+ userEvent.clear(screen.getByLabelText(/cronLine/));
70
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
71
+ await wait();
72
+ expect(
73
+ screen.queryByText('is not a valid cronline')
74
+ ).not.toBeInTheDocument();
75
+ expect(submitBtn).not.toBeDisabled();
76
+ userEvent.click(submitBtn);
77
+ await wait(2);
78
+ expect(pushMock).toHaveBeenCalledWith(ovalPoliciesPath);
79
+ expect(showToast).toHaveBeenCalledWith({
80
+ type: 'success',
81
+ message: 'OVAL Policy succesfully created.',
82
+ });
83
+ });
84
+ it('should not create new policy on validation error', async () => {
85
+ const showToast = jest.fn();
86
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
87
+ const pushMock = jest.fn();
88
+
89
+ render(
90
+ <TestComponent
91
+ mocks={ovalContentMocks.concat(policyValidationMock)}
92
+ history={{
93
+ push: pushMock,
94
+ }}
95
+ />
96
+ );
97
+ await wait();
98
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
99
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
100
+ userEvent.selectOptions(
101
+ screen.getByLabelText(/ovalContentId/),
102
+ newPolicyContentName
103
+ );
104
+ await wait();
105
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
106
+ await wait(2);
107
+ expect(pushMock).not.toHaveBeenCalled();
108
+ expect(showToast).not.toHaveBeenCalled();
109
+ expect(screen.getByText('has already been taken')).toBeInTheDocument();
110
+ });
111
+ it('should not create policy on preconditions error', async () => {
112
+ const showToast = jest.fn();
113
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
114
+ const pushMock = jest.fn();
115
+
116
+ render(
117
+ <TestComponent
118
+ mocks={ovalContentMocks.concat(policyPreconditionMock)}
119
+ history={{
120
+ push: pushMock,
121
+ }}
122
+ />
123
+ );
124
+ await wait();
125
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
126
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
127
+ userEvent.selectOptions(
128
+ screen.getByLabelText(/ovalContentId/),
129
+ newPolicyContentName
130
+ );
131
+ await wait();
132
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
133
+ await wait(2);
134
+ await wait();
135
+ expect(pushMock).not.toHaveBeenCalled();
136
+ expect(showToast).toHaveBeenCalledWith({
137
+ type: 'error',
138
+ message: roleAbsentCheck.failMsg,
139
+ });
140
+ });
141
+ it('should show hostgroup errros', async () => {
142
+ const showToast = jest.fn();
143
+ jest.spyOn(toasts, 'showToast').mockImplementation(() => showToast);
144
+ const pushMock = jest.fn();
145
+
146
+ render(
147
+ <TestComponent
148
+ mocks={ovalContentMocks
149
+ .concat(policyInvalidHgMock)
150
+ .concat(hostgroupsMock)}
151
+ history={{
152
+ push: pushMock,
153
+ }}
154
+ />
155
+ );
156
+ await wait();
157
+ userEvent.type(screen.getByLabelText(/name/), newPolicyName);
158
+ userEvent.type(screen.getByLabelText(/cronLine/), newPolicyCronline);
159
+ userEvent.selectOptions(
160
+ screen.getByLabelText(/ovalContentId/),
161
+ newPolicyContentName
162
+ );
163
+ userEvent.type(screen.getByLabelText(/hostgroup/), 'first');
164
+ await wait(500);
165
+ userEvent.click(screen.getByText(firstHg.name));
166
+ await wait();
167
+ userEvent.click(screen.getByRole('button', { name: 'submit' }));
168
+ await wait(2);
169
+ expect(pushMock).not.toHaveBeenCalled();
170
+ expect(screen.getByText(withoutProxyCheck.failMsg)).toBeInTheDocument();
171
+ });
172
+ });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { useDispatch } from 'react-redux';
3
+
4
+ import { showToast } from '../../../helpers/toastsHelper';
5
+ import OvalPoliciesNew from './OvalPoliciesNew';
6
+
7
+ const WrappedOvalPoliciesNew = props => (
8
+ <OvalPoliciesNew {...props} showToast={showToast(useDispatch())} />
9
+ );
10
+
11
+ export default WrappedOvalPoliciesNew;
@@ -4,7 +4,7 @@ import { translate as __ } from 'foremanReact/common/I18n';
4
4
 
5
5
  import { linkCell } from '../../../helpers/tableHelper';
6
6
  import { hostsPath } from '../../../helpers/pathsHelper';
7
- import { decodeId } from '../../../helpers/globalIdHelper';
7
+ import { decodeModelId } from '../../../helpers/globalIdHelper';
8
8
  import { addSearch } from '../../../helpers/pageParamsHelper';
9
9
 
10
10
  import withLoading from '../../../components/withLoading';
@@ -25,7 +25,7 @@ const CvesTable = props => {
25
25
 
26
26
  const hostCount = cve =>
27
27
  linkCell(
28
- addSearch(hostsPath, { search: `cve_id = ${decodeId(cve)}` }),
28
+ addSearch(hostsPath, { search: `cve_id = ${decodeModelId(cve)}` }),
29
29
  cve.hosts.nodes.length
30
30
  );
31
31
 
@@ -40,6 +40,7 @@ const DetailsTab = props => {
40
40
  onConfirm={onAttrUpdate('name', policy, callMutation, showToast)}
41
41
  component={TextInput}
42
42
  attrName="name"
43
+ allowed={policy.meta.canEdit}
43
44
  />
44
45
  </TextListItem>
45
46
  <TextListItem component={TextListItemVariants.dt}>
@@ -70,6 +71,7 @@ const DetailsTab = props => {
70
71
  )}
71
72
  component={TextArea}
72
73
  attrName="description"
74
+ allowed={policy.meta.canEdit}
73
75
  />
74
76
  </TextListItem>
75
77
  </TextList>
@@ -1,6 +1,5 @@
1
1
  import { translate as __, sprintf } from 'foremanReact/common/I18n';
2
-
3
- import { decodeId } from '../../../helpers/globalIdHelper';
2
+ import { decodeModelId } from '../../../helpers/globalIdHelper';
4
3
  import { addSearch } from '../../../helpers/pageParamsHelper';
5
4
  import { newJobPath } from '../../../helpers/pathsHelper';
6
5
 
@@ -19,7 +18,9 @@ export const policySchedule = policy => {
19
18
 
20
19
  const targetingScopedSearchQuery = policy => {
21
20
  const hgIds = policy.hostgroups.nodes.reduce((memo, hg) => {
22
- const ids = [decodeId(hg)].concat(hg.descendants.nodes.map(decodeId));
21
+ const ids = [decodeModelId(hg)].concat(
22
+ hg.descendants.nodes.map(decodeModelId)
23
+ );
23
24
  return ids.reduce(
24
25
  (acc, id) => (acc.includes(id) ? acc : [...acc, id]),
25
26
  memo
@@ -9,6 +9,7 @@ import {
9
9
  historyMock,
10
10
  ovalPolicyId,
11
11
  policyDetailMock,
12
+ policyEditPermissionsMock,
12
13
  ovalPolicy,
13
14
  } from './OvalPoliciesShow.fixtures';
14
15
  import {
@@ -171,5 +172,31 @@ describe('OvalPoliciesShow', () => {
171
172
  ).not.toBeInTheDocument();
172
173
  expect(screen.getByText(ovalPolicy.name)).toBeInTheDocument();
173
174
  expect(screen.getByText('has already been taken')).toBeInTheDocument();
175
+ userEvent.click(
176
+ screen.getByRole('button', { name: 'cancel editing name' })
177
+ );
178
+ userEvent.click(editBtn);
179
+ expect(
180
+ screen.queryByText('has already been taken')
181
+ ).not.toBeInTheDocument();
182
+ });
183
+ it('should not show edit btns when user is not allowed to edit', async () => {
184
+ render(
185
+ <TestComponent
186
+ history={historyMock}
187
+ match={{
188
+ params: { id: ovalPolicyId, tab: 'details' },
189
+ path: ovalPoliciesShowPath,
190
+ }}
191
+ mocks={policyEditPermissionsMock}
192
+ />
193
+ );
194
+ await waitFor(tick);
195
+ expect(
196
+ screen.queryByRole('button', { name: 'edit name' })
197
+ ).not.toBeInTheDocument();
198
+ expect(
199
+ screen.queryByRole('button', { name: 'edit description' })
200
+ ).not.toBeInTheDocument();
174
201
  });
175
202
  });
@@ -1,4 +1,4 @@
1
- import { mockFactory, admin, intruder } from '../../../../testHelper';
1
+ import { mockFactory, admin, intruder, viewer } from '../../../../testHelper';
2
2
  import ovalPolicyQuery from '../../../../graphql/queries/ovalPolicy.gql';
3
3
  import cvesQuery from '../../../../graphql/queries/cves.gql';
4
4
  import hostgroupsQuery from '../../../../graphql/queries/hostgroups.gql';
@@ -16,6 +16,9 @@ export const ovalPolicy = {
16
16
  weekday: 'tuesday',
17
17
  dayOfMonth: null,
18
18
  description: 'A very strict policy',
19
+ meta: {
20
+ canEdit: true,
21
+ },
19
22
  hostgroups: {
20
23
  nodes: [
21
24
  {
@@ -33,6 +36,8 @@ export const ovalPolicy = {
33
36
  },
34
37
  };
35
38
 
39
+ const noEditPolicy = { ...ovalPolicy, meta: { canEdit: false } };
40
+
36
41
  const cvesResult = {
37
42
  totalCount: 1,
38
43
  nodes: [
@@ -112,3 +117,8 @@ export const policyHostgroupsDeniedMock = hostgroupsMockFactory(
112
117
  { totalCount: 0, nodes: [] },
113
118
  { currentUser: intruder }
114
119
  );
120
+ export const policyEditPermissionsMock = policyDetailMockFactory(
121
+ { id: ovalPolicy.id },
122
+ noEditPolicy,
123
+ { currentUser: viewer }
124
+ );
@@ -3,6 +3,7 @@ import OvalContentsIndex from './OvalContents/OvalContentsIndex';
3
3
  import OvalContentsShow from './OvalContents/OvalContentsShow';
4
4
  import OvalContentsNew from './OvalContents/OvalContentsNew';
5
5
  import OvalPoliciesIndex from './OvalPolicies/OvalPoliciesIndex';
6
+ import OvalPoliciesNew from './OvalPolicies/OvalPoliciesNew';
6
7
  import OvalPoliciesShow from './OvalPolicies/OvalPoliciesShow';
7
8
 
8
9
  import {
@@ -11,6 +12,7 @@ import {
11
12
  ovalContentsNewPath,
12
13
  ovalPoliciesPath,
13
14
  ovalPoliciesShowPath,
15
+ ovalPoliciesNewPath,
14
16
  } from '../helpers/pathsHelper';
15
17
 
16
18
  export default [
@@ -34,6 +36,11 @@ export default [
34
36
  render: props => <OvalPoliciesIndex {...props} />,
35
37
  exact: true,
36
38
  },
39
+ {
40
+ path: ovalPoliciesNewPath,
41
+ render: props => <OvalPoliciesNew {...props} />,
42
+ exact: true,
43
+ },
37
44
  {
38
45
  path: ovalPoliciesShowPath,
39
46
  render: props => <OvalPoliciesShow {...props} />,
@@ -4,6 +4,7 @@ import store from 'foremanReact/redux';
4
4
  import { MockedProvider } from '@apollo/react-testing';
5
5
  import { MemoryRouter } from 'react-router-dom';
6
6
  import { getForemanContext } from 'foremanReact/Root/Context/ForemanContext';
7
+ import { waitFor } from '@testing-library/react';
7
8
 
8
9
  export const withRedux = Component => props => (
9
10
  <Provider store={store}>
@@ -42,6 +43,14 @@ export const withMockedProvider = Component => props => {
42
43
  // use to resolve async mock requests for apollo MockedProvider
43
44
  export const tick = () => new Promise(resolve => setTimeout(resolve, 0));
44
45
 
46
+ export const wait = async (tickCount = 1) => {
47
+ for (let i = 1; i < tickCount; i++) {
48
+ // eslint-disable-next-line no-await-in-loop
49
+ await waitFor(tick);
50
+ }
51
+ return waitFor(tick);
52
+ };
53
+
45
54
  export const historyMock = {
46
55
  location: {
47
56
  search: '',
@@ -76,6 +85,19 @@ export const intruder = userFactory('intruder', [
76
85
  },
77
86
  ]);
78
87
 
88
+ export const viewer = userFactory('viewer', [
89
+ {
90
+ __typename: 'Permission',
91
+ id: 'MDE6UGVybWlzc2lvbi0yOTY=',
92
+ name: 'view_oval_contents',
93
+ },
94
+ {
95
+ __typename: 'Permission',
96
+ id: 'MDE6UGVybWlzc2lvbi0yNzU=',
97
+ name: 'view_oval_policies',
98
+ },
99
+ ]);
100
+
79
101
  export const mockFactory = (resultName, query) => (
80
102
  variables,
81
103
  modelResults,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_openscap
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - slukasik@redhat.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-11-01 00:00:00.000000000 Z
11
+ date: 2021-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -73,6 +73,7 @@ files:
73
73
  - app/controllers/scap_contents_controller.rb
74
74
  - app/controllers/tailoring_files_controller.rb
75
75
  - app/graphql/mutations/oval_contents/delete.rb
76
+ - app/graphql/mutations/oval_policies/create.rb
76
77
  - app/graphql/mutations/oval_policies/delete.rb
77
78
  - app/graphql/mutations/oval_policies/update.rb
78
79
  - app/graphql/types/cve.rb
@@ -444,6 +445,7 @@ files:
444
445
  - webpack/components/withDeleteModal.js
445
446
  - webpack/components/withLoading.js
446
447
  - webpack/global_index.js
448
+ - webpack/graphql/mutations/createOvalPolicy.gql
447
449
  - webpack/graphql/mutations/deleteOvalContent.gql
448
450
  - webpack/graphql/mutations/deleteOvalPolicy.gql
449
451
  - webpack/graphql/mutations/updateOvalPolicy.gql
@@ -463,6 +465,7 @@ files:
463
465
  - webpack/helpers/permissionsHelper.js
464
466
  - webpack/helpers/tableHelper.js
465
467
  - webpack/helpers/toastHelper.js
468
+ - webpack/helpers/toastsHelper.js
466
469
  - webpack/index.js
467
470
  - webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js
468
471
  - webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js
@@ -487,6 +490,13 @@ files:
487
490
  - webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js
488
491
  - webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js
489
492
  - webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js
493
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/HostgroupSelect.js
494
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyForm.js
495
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/NewOvalPolicyFormHelpers.js
496
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/OvalPoliciesNew.js
497
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.fixtures.js
498
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/__tests__/OvalPoliciesNew.test.js
499
+ - webpack/routes/OvalPolicies/OvalPoliciesNew/index.js
490
500
  - webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js
491
501
  - webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js
492
502
  - webpack/routes/OvalPolicies/OvalPoliciesShow/DetailsTab.js