foreman_openscap 4.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +0 -6
  3. data/app/controllers/api/v2/compliance/oval_policies_controller.rb +1 -1
  4. data/app/helpers/arf_report_dashboard_helper.rb +2 -4
  5. data/app/helpers/compliance_hosts_helper.rb +1 -1
  6. data/app/helpers/policies_helper.rb +1 -1
  7. data/app/services/foreman_openscap/client_config/base.rb +1 -0
  8. data/app/services/foreman_openscap/client_config/puppet.rb +6 -2
  9. data/app/views/api/v2/compliance/oval_contents/destroy.json.rabl +3 -0
  10. data/app/views/arf_reports/_metrics.html.erb +4 -4
  11. data/app/views/compliance_hosts/show.html.erb +4 -6
  12. data/app/views/dashboard/_compliance_reports_breakdown_widget.html.erb +4 -3
  13. data/app/views/policy_dashboard/_policy_chart_widget.html.erb +3 -2
  14. data/db/migrate/20200117135424_migrate_port_overrides_to_int.rb +2 -1
  15. data/db/migrate/20201202110213_update_puppet_port_param_type.rb +2 -1
  16. data/lib/foreman_openscap/engine.rb +0 -7
  17. data/lib/foreman_openscap/version.rb +1 -1
  18. data/package.json +48 -0
  19. data/test/functional/api/v2/compliance/oval_reports_controller_test.rb +1 -1
  20. data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -0
  21. data/test/helpers/arf_report_dashboard_helper_test.rb +9 -10
  22. data/test/helpers/policy_dashboard_helper_test.rb +1 -1
  23. data/test/test_plugin_helper.rb +9 -4
  24. data/test/unit/policy_test.rb +1 -1
  25. data/test/unit/services/config_name_service_test.rb +1 -0
  26. data/test/unit/services/hostgroup_overrider_test.rb +2 -1
  27. data/test/unit/services/lookup_key_overrider_test.rb +4 -1
  28. data/webpack/components/EmptyState.js +73 -0
  29. data/webpack/components/IndexLayout.js +35 -0
  30. data/webpack/components/IndexLayout.scss +3 -0
  31. data/webpack/components/IndexTable/IndexTableHelper.js +9 -0
  32. data/webpack/components/IndexTable/index.js +65 -0
  33. data/webpack/components/RuleSeverity/RuleSeverity.scss +3 -0
  34. data/webpack/components/RuleSeverity/RuleSeverity.test.js +13 -0
  35. data/webpack/components/RuleSeverity/__snapshots__/RuleSeverity.test.js.snap +41 -0
  36. data/webpack/components/RuleSeverity/i_severity-critical.svg +61 -0
  37. data/webpack/components/RuleSeverity/i_severity-high.svg +61 -0
  38. data/webpack/components/RuleSeverity/i_severity-low.svg +62 -0
  39. data/webpack/components/RuleSeverity/i_severity-med.svg +62 -0
  40. data/webpack/components/RuleSeverity/i_unknown.svg +33 -0
  41. data/webpack/components/RuleSeverity/index.js +33 -0
  42. data/webpack/components/withLoading.js +87 -0
  43. data/webpack/global_index.js +5 -0
  44. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  45. data/webpack/graphql/queries/cves.gql +23 -0
  46. data/webpack/graphql/queries/ovalContents.gql +16 -0
  47. data/webpack/graphql/queries/ovalPolicies.gql +17 -0
  48. data/webpack/graphql/queries/ovalPolicy.gql +26 -0
  49. data/webpack/helpers/commonHelper.js +1 -0
  50. data/webpack/helpers/globalIdHelper.js +13 -0
  51. data/webpack/helpers/pageParamsHelper.js +31 -0
  52. data/webpack/helpers/pathsHelper.js +22 -0
  53. data/webpack/helpers/permissionsHelper.js +42 -0
  54. data/webpack/helpers/tableHelper.js +9 -0
  55. data/webpack/index.js +8 -0
  56. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsIndex.js +46 -0
  57. data/webpack/routes/OvalContents/OvalContentsIndex/OvalContentsTable.js +46 -0
  58. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.fixtures.js +120 -0
  59. data/webpack/routes/OvalContents/OvalContentsIndex/__tests__/OvalContentsIndex.test.js +101 -0
  60. data/webpack/routes/OvalContents/OvalContentsIndex/index.js +7 -0
  61. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesIndex.js +47 -0
  62. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/OvalPoliciesTable.js +44 -0
  63. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.fixtures.js +95 -0
  64. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/__tests__/OvalPoliciesIndex.test.js +98 -0
  65. data/webpack/routes/OvalPolicies/OvalPoliciesIndex/index.js +7 -0
  66. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTab.js +49 -0
  67. data/webpack/routes/OvalPolicies/OvalPoliciesShow/CvesTable.js +63 -0
  68. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShow.js +78 -0
  69. data/webpack/routes/OvalPolicies/OvalPoliciesShow/OvalPoliciesShowHelper.js +39 -0
  70. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.fixtures.js +87 -0
  71. data/webpack/routes/OvalPolicies/OvalPoliciesShow/__tests__/OvalPoliciesShow.test.js +129 -0
  72. data/webpack/routes/OvalPolicies/OvalPoliciesShow/index.js +36 -0
  73. data/webpack/routes/routes.js +28 -0
  74. data/webpack/testHelper.js +96 -0
  75. metadata +56 -7
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import withLoading from '../../../components/withLoading';
6
+ import IndexTable from '../../../components/IndexTable';
7
+
8
+ const OvalContentsTable = props => {
9
+ const columns = [
10
+ { title: __('Name') },
11
+ { title: __('URL') },
12
+ { title: __('Original File Name') },
13
+ ];
14
+
15
+ const rows = props.ovalContents.map(ovalContent => ({
16
+ cells: [
17
+ { title: ovalContent.name },
18
+ { title: ovalContent.url || '' },
19
+ { title: ovalContent.originalFilename || '' },
20
+ ],
21
+ ovalContent,
22
+ }));
23
+
24
+ const actions = [];
25
+
26
+ return (
27
+ <IndexTable
28
+ columns={columns}
29
+ rows={rows}
30
+ actions={actions}
31
+ pagination={props.pagination}
32
+ totalCount={props.totalCount}
33
+ history={props.history}
34
+ ariaTableLabel={__('OVAL Contents table')}
35
+ />
36
+ );
37
+ };
38
+
39
+ OvalContentsTable.propTypes = {
40
+ ovalContents: PropTypes.array.isRequired,
41
+ pagination: PropTypes.object.isRequired,
42
+ totalCount: PropTypes.number.isRequired,
43
+ history: PropTypes.object.isRequired,
44
+ };
45
+
46
+ export default withLoading(OvalContentsTable);
@@ -0,0 +1,120 @@
1
+ import ovalContentsQuery from '../../../../graphql/queries/ovalContents.gql';
2
+ import { ovalContentsPath } from '../../../../helpers/pathsHelper';
3
+ import {
4
+ mockFactory,
5
+ admin,
6
+ intruder,
7
+ userFactory,
8
+ } from '../../../../testHelper';
9
+
10
+ const ovalContentMockFactory = mockFactory('ovalContents', ovalContentsQuery);
11
+
12
+ const ovalContents = {
13
+ totalCount: 4,
14
+ nodes: [
15
+ {
16
+ __typename: 'ForemanOpenscap::OvalContent',
17
+ id: 'abc',
18
+ name: 'ansible OVAL content',
19
+ url:
20
+ 'http://oval-content-source/security/data/oval/ansible-2-including-unpatched.oval.xml.bz2',
21
+ originalFilename: '',
22
+ },
23
+ {
24
+ __typename: 'ForemanOpenscap::OvalContent',
25
+ id: 'bcd',
26
+ name: 'dotnet OVAL content',
27
+ url:
28
+ 'http://oval-content-source/security/data/oval/dotnet-2.2.oval.xml.bz2',
29
+ originalFilename: '',
30
+ },
31
+ {
32
+ __typename: 'ForemanOpenscap::OvalContent',
33
+ id: 'cde',
34
+ name: 'jboss OVAL content',
35
+ url: '',
36
+ originalFilename: 'jboss.oval.xml.bz2',
37
+ },
38
+ {
39
+ __typename: 'ForemanOpenscap::OvalContent',
40
+ id: 'def',
41
+ name: 'openshift OVAL content',
42
+ url: '',
43
+ originalFilename: 'openshift.oval.xml.bz2',
44
+ },
45
+ ],
46
+ };
47
+
48
+ const paginatedOvalContents = {
49
+ totalCount: 7,
50
+ nodes: [
51
+ {
52
+ __typename: 'ForemanOpenscap::OvalContent',
53
+ id: 'bcd',
54
+ name: 'dotnet OVAL content',
55
+ url:
56
+ 'http://oval-content-source/security/data/oval/dotnet-2.2.oval.xml.bz2',
57
+ originalFilename: '',
58
+ },
59
+ {
60
+ __typename: 'ForemanOpenscap::OvalContent',
61
+ id: 'def',
62
+ name: 'openshift OVAL content',
63
+ url: '',
64
+ originalFilename: 'openshift.oval.xml.bz2',
65
+ },
66
+ ],
67
+ };
68
+
69
+ const viewer = userFactory('viewer', [
70
+ {
71
+ __typename: 'Permission',
72
+ id: 'MDE6UGVybWlzc2lvbi0yOTY=',
73
+ name: 'view_oval_contents',
74
+ },
75
+ ]);
76
+
77
+ export const mocks = ovalContentMockFactory(
78
+ { first: 20, last: 20 },
79
+ ovalContents,
80
+ { currentUser: admin }
81
+ );
82
+
83
+ export const paginatedMocks = ovalContentMockFactory(
84
+ { first: 10, last: 5 },
85
+ paginatedOvalContents,
86
+ { currentUser: admin }
87
+ );
88
+
89
+ export const emptyMocks = ovalContentMockFactory(
90
+ { first: 20, last: 20 },
91
+ { totalCount: 0, nodes: [] },
92
+ { currentUser: admin }
93
+ );
94
+ export const errorMocks = ovalContentMockFactory(
95
+ { first: 20, last: 20 },
96
+ { totalCount: 0, nodes: [] },
97
+ { errors: [{ message: 'Something very bad happened.' }], currentUser: admin }
98
+ );
99
+
100
+ export const viewerMocks = ovalContentMockFactory(
101
+ { first: 20, last: 20 },
102
+ ovalContents,
103
+ { currentUser: viewer }
104
+ );
105
+
106
+ export const unauthorizedMocks = ovalContentMockFactory(
107
+ { first: 20, last: 20 },
108
+ ovalContents,
109
+ { currentUser: intruder }
110
+ );
111
+
112
+ export const pushMock = jest.fn();
113
+
114
+ export const pagePaginationHistoryMock = {
115
+ location: {
116
+ search: '?page=2&perPage=5',
117
+ pathname: ovalContentsPath,
118
+ },
119
+ push: pushMock,
120
+ };
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { within } from '@testing-library/dom';
4
+ import userEvent from '@testing-library/user-event';
5
+ import '@testing-library/jest-dom';
6
+
7
+ import OvalContentsIndex from '../OvalContentsIndex';
8
+
9
+ import { withMockedProvider, tick, historyMock } from '../../../../testHelper';
10
+ import { ovalContentsPath } from '../../../../helpers/pathsHelper';
11
+
12
+ import {
13
+ mocks,
14
+ paginatedMocks,
15
+ pushMock,
16
+ pagePaginationHistoryMock,
17
+ emptyMocks,
18
+ errorMocks,
19
+ viewerMocks,
20
+ unauthorizedMocks,
21
+ } from './OvalContentsIndex.fixtures';
22
+
23
+ const TestComponent = withMockedProvider(OvalContentsIndex);
24
+
25
+ describe('OvalContentsIndex', () => {
26
+ it('should load page', async () => {
27
+ const { container } = render(
28
+ <TestComponent history={historyMock} mocks={mocks} />
29
+ );
30
+ expect(screen.getByText('Loading')).toBeInTheDocument();
31
+ await waitFor(tick);
32
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
33
+ expect(screen.getByText('ansible OVAL content')).toBeInTheDocument();
34
+ expect(
35
+ screen.getByText(
36
+ 'http://oval-content-source/security/data/oval/ansible-2-including-unpatched.oval.xml.bz2'
37
+ )
38
+ ).toBeInTheDocument();
39
+ expect(screen.getByText('openshift OVAL content')).toBeInTheDocument();
40
+ expect(screen.getByText('openshift.oval.xml.bz2')).toBeInTheDocument();
41
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
42
+ expect(within(pageItems).getByText(/1 - 4/)).toBeInTheDocument();
43
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
44
+ expect(within(pageItems).getByText('4')).toBeInTheDocument();
45
+ });
46
+ it('should load page with pagination params', async () => {
47
+ const { container } = render(
48
+ <TestComponent
49
+ history={pagePaginationHistoryMock}
50
+ mocks={paginatedMocks}
51
+ />
52
+ );
53
+ await waitFor(tick);
54
+ const pageItems = container.querySelector('.pf-c-pagination__total-items');
55
+ expect(within(pageItems).getByText(/6 - 7/)).toBeInTheDocument();
56
+ expect(within(pageItems).getByText('of')).toBeInTheDocument();
57
+ expect(within(pageItems).getByText('7')).toBeInTheDocument();
58
+ userEvent.click(
59
+ screen.getByRole('button', { name: 'Go to previous page' })
60
+ );
61
+
62
+ expect(pushMock).toHaveBeenCalledWith(
63
+ `${ovalContentsPath}?page=1&perPage=5`
64
+ );
65
+ });
66
+ it('should show empty state', async () => {
67
+ render(<TestComponent history={historyMock} mocks={emptyMocks} />);
68
+ expect(screen.getByText('Loading')).toBeInTheDocument();
69
+ await waitFor(tick);
70
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
71
+ expect(screen.getByText('No OVAL Contents found.')).toBeInTheDocument();
72
+ });
73
+ it('should show errors', async () => {
74
+ render(<TestComponent history={historyMock} mocks={errorMocks} />);
75
+ expect(screen.getByText('Loading')).toBeInTheDocument();
76
+ await waitFor(tick);
77
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
78
+ expect(
79
+ screen.getByText('Something very bad happened.')
80
+ ).toBeInTheDocument();
81
+ expect(screen.getByText('Error!')).toBeInTheDocument();
82
+ });
83
+ it('should load page for user with permissions', async () => {
84
+ render(<TestComponent history={historyMock} mocks={viewerMocks} />);
85
+ await waitFor(tick);
86
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
87
+ expect(screen.getByText('ansible OVAL content')).toBeInTheDocument();
88
+ });
89
+ it('should not load page for user without permissions', async () => {
90
+ render(<TestComponent history={historyMock} mocks={unauthorizedMocks} />);
91
+ await waitFor(tick);
92
+ expect(screen.queryByText('Loading')).not.toBeInTheDocument();
93
+ expect(screen.queryByText('ansible OVAL content')).not.toBeInTheDocument();
94
+ expect(
95
+ screen.getByText(
96
+ 'You are not authorized to view the page. Request the following permissions from administrator: view_oval_contents.'
97
+ )
98
+ ).toBeInTheDocument();
99
+ expect(screen.getByText('Permission denied')).toBeInTheDocument();
100
+ });
101
+ });
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ import OvalContentsIndex from './OvalContentsIndex';
4
+
5
+ const WrappedOvalContentsIndex = props => <OvalContentsIndex {...props} />;
6
+
7
+ export default WrappedOvalContentsIndex;
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useQuery } from '@apollo/client';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import OvalPoliciesTable from './OvalPoliciesTable';
7
+ import IndexLayout from '../../../components/IndexLayout';
8
+
9
+ import {
10
+ useParamsToVars,
11
+ useCurrentPagination,
12
+ } from '../../../helpers/pageParamsHelper';
13
+ import policiesQuery from '../../../graphql/queries/ovalPolicies.gql';
14
+
15
+ const OvalPoliciesIndex = props => {
16
+ const pagination = useCurrentPagination(props.history);
17
+
18
+ const useFetchFn = componentProps =>
19
+ useQuery(policiesQuery, {
20
+ variables: useParamsToVars(componentProps.history),
21
+ });
22
+
23
+ const renameData = data => ({
24
+ policies: data.ovalPolicies.nodes,
25
+ totalCount: data.ovalPolicies.totalCount,
26
+ });
27
+
28
+ return (
29
+ <IndexLayout pageTitle={__('OVAL Policies')}>
30
+ <OvalPoliciesTable
31
+ {...props}
32
+ fetchFn={useFetchFn}
33
+ renameData={renameData}
34
+ resultPath="ovalPolicies.nodes"
35
+ pagination={pagination}
36
+ emptyStateTitle={__('No OVAL Policies found')}
37
+ permissions={['view_oval_policies']}
38
+ />
39
+ </IndexLayout>
40
+ );
41
+ };
42
+
43
+ OvalPoliciesIndex.propTypes = {
44
+ history: PropTypes.object.isRequired,
45
+ };
46
+
47
+ export default OvalPoliciesIndex;
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import IndexTable from '../../../components/IndexTable';
6
+ import withLoading from '../../../components/withLoading';
7
+
8
+ import { linkCell } from '../../../helpers/tableHelper';
9
+ import { ovalPoliciesPath, modelPath } from '../../../helpers/pathsHelper';
10
+
11
+ const OvalPoliciesTable = props => {
12
+ const columns = [{ title: __('Name') }, { title: __('OVAL Content') }];
13
+
14
+ const rows = props.policies.map(policy => ({
15
+ cells: [
16
+ { title: linkCell(modelPath(ovalPoliciesPath, policy), policy.name) },
17
+ { title: policy.ovalContent.name },
18
+ ],
19
+ policy,
20
+ }));
21
+
22
+ const actions = [];
23
+
24
+ return (
25
+ <IndexTable
26
+ columns={columns}
27
+ rows={rows}
28
+ actions={actions}
29
+ pagination={props.pagination}
30
+ totalCount={props.totalCount}
31
+ history={props.history}
32
+ ariaTableLabel={__('OVAL Policies Table')}
33
+ />
34
+ );
35
+ };
36
+
37
+ OvalPoliciesTable.propTypes = {
38
+ policies: PropTypes.array.isRequired,
39
+ pagination: PropTypes.object.isRequired,
40
+ totalCount: PropTypes.number.isRequired,
41
+ history: PropTypes.object.isRequired,
42
+ };
43
+
44
+ export default withLoading(OvalPoliciesTable);
@@ -0,0 +1,95 @@
1
+ import policiesQuery from '../../../../graphql/queries/ovalPolicies.gql';
2
+ import { ovalPoliciesPath } from '../../../../helpers/pathsHelper';
3
+ import {
4
+ mockFactory,
5
+ admin,
6
+ intruder,
7
+ userFactory,
8
+ } from '../../../../testHelper';
9
+
10
+ const policiesMockFactory = mockFactory('ovalPolicies', policiesQuery);
11
+
12
+ export const pushMock = jest.fn();
13
+
14
+ export const pageParamsHistoryMock = {
15
+ location: {
16
+ search: '?page=2&perPage=5',
17
+ pathname: ovalPoliciesPath,
18
+ },
19
+ push: pushMock,
20
+ };
21
+
22
+ const policiesMocks = {
23
+ totalCount: 2,
24
+ nodes: [
25
+ {
26
+ __typename: 'ForemanOpenscap::OvalPolicy',
27
+ id: 'abc',
28
+ name: 'first policy',
29
+ ovalContent: { name: 'first content' },
30
+ },
31
+ {
32
+ __typename: 'ForemanOpenscap::OvalPolicy',
33
+ id: 'xyz',
34
+ name: 'second policy',
35
+ ovalContent: { name: 'second content' },
36
+ },
37
+ ],
38
+ };
39
+
40
+ const pagedPoliciesMocks = {
41
+ totalCount: 7,
42
+ nodes: [
43
+ {
44
+ __typename: 'ForemanOpenscap::OvalPolicy',
45
+ id: 'xyz',
46
+ name: 'sixth policy',
47
+ ovalContent: { name: 'sixth content' },
48
+ },
49
+ {
50
+ __typename: 'ForemanOpenscap::OvalPolicy',
51
+ id: 'abc',
52
+ name: 'seventh policy',
53
+ ovalContent: { name: 'seventh content' },
54
+ },
55
+ ],
56
+ };
57
+
58
+ const viewer = userFactory('viewer', [
59
+ {
60
+ __typename: 'Permission',
61
+ id: 'MDE6UGVybWlzc2lvbi0yOTY=',
62
+ name: 'view_oval_policies',
63
+ },
64
+ ]);
65
+
66
+ export const mocks = policiesMockFactory(
67
+ { first: 20, last: 20 },
68
+ policiesMocks,
69
+ { currentUser: admin }
70
+ );
71
+ export const pageParamsMocks = policiesMockFactory(
72
+ { first: 10, last: 5 },
73
+ pagedPoliciesMocks,
74
+ { currentUser: admin }
75
+ );
76
+ export const emptyMocks = policiesMockFactory(
77
+ { first: 20, last: 20 },
78
+ { totalCount: 0, nodes: [] },
79
+ { currentUser: admin }
80
+ );
81
+ export const errorMocks = policiesMockFactory(
82
+ { first: 20, last: 20 },
83
+ { totalCount: 0, nodes: [] },
84
+ { errors: [{ message: 'Something very bad happened.' }], currentUser: admin }
85
+ );
86
+ export const viewerMocks = policiesMockFactory(
87
+ { first: 20, last: 20 },
88
+ policiesMocks,
89
+ { currentUser: viewer }
90
+ );
91
+ export const unauthorizedMocks = policiesMockFactory(
92
+ { first: 20, last: 20 },
93
+ policiesMocks,
94
+ { currentUser: intruder }
95
+ );