@mitre/inspec-objects 0.0.13 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,15 +3,16 @@ export declare function objectifyDescriptions(descs: ExecJSON.ControlDescription
3
3
  [key: string]: string | undefined;
4
4
  } | null | undefined): {
5
5
  [key: string]: string | undefined;
6
- } | null | undefined;
6
+ };
7
7
  export default class Control {
8
- id?: string | null;
8
+ id: string;
9
9
  title?: string | null;
10
10
  code?: string | null;
11
+ describe?: string | null;
11
12
  desc?: string | null;
12
- descs?: {
13
+ descs: {
13
14
  [key: string]: string | undefined;
14
- } | null;
15
+ };
15
16
  impact?: number;
16
17
  ref?: string;
17
18
  refs?: (string | {
@@ -13,7 +13,7 @@ function objectifyDescriptions(descs) {
13
13
  });
14
14
  return descriptions;
15
15
  }
16
- return descs;
16
+ return descs || {};
17
17
  }
18
18
  exports.objectifyDescriptions = objectifyDescriptions;
19
19
  class Control {
@@ -31,7 +31,7 @@ class Control {
31
31
  const flattened = (0, flat_1.flatten)(this);
32
32
  Object.entries(flattened).forEach(([key, value]) => {
33
33
  if (typeof value === 'string') {
34
- lodash_1.default.set(flattened, key, (0, global_1.unformatText)(value));
34
+ lodash_1.default.set(flattened, key, value);
35
35
  }
36
36
  });
37
37
  return new Control((0, flat_1.unflatten)(flattened));
@@ -40,13 +40,13 @@ class Control {
40
40
  let result = "# encoding: UTF-8\n\n";
41
41
  result += `control "${this.id}" do\n`;
42
42
  if (this.title) {
43
- result += ` title "${(0, global_1.wrapAndEscapeQuotes)((0, global_1.removeNewlinePlaceholders)(this.title), lineLength)}"\n`;
43
+ result += ` title "${(0, global_1.escapeDoubleQuotes)((0, global_1.removeNewlinePlaceholders)(this.title))}"\n`;
44
44
  }
45
45
  else {
46
46
  console.error(`${this.id} does not have a title`);
47
47
  }
48
48
  if (this.desc) {
49
- result += ` desc "${(0, global_1.wrapAndEscapeQuotes)((0, global_1.removeNewlinePlaceholders)(this.desc), lineLength)}"\n`;
49
+ result += ` desc "${(0, global_1.escapeDoubleQuotes)((0, global_1.removeNewlinePlaceholders)(this.desc))}"\n`;
50
50
  }
51
51
  else {
52
52
  console.error(`${this.id} does not have a desc`);
@@ -54,7 +54,7 @@ class Control {
54
54
  if (this.descs) {
55
55
  Object.entries(this.descs).forEach(([key, desc]) => {
56
56
  if (desc) {
57
- result += ` desc "${key}", "${(0, global_1.wrapAndEscapeQuotes)((0, global_1.removeNewlinePlaceholders)(desc), lineLength)}"\n`;
57
+ result += ` desc "${key}", "${(0, global_1.escapeDoubleQuotes)((0, global_1.removeNewlinePlaceholders)(desc))}"\n`;
58
58
  }
59
59
  else {
60
60
  console.error(`${this.id} does not have a desc for the value ${key}`);
@@ -81,7 +81,7 @@ class Control {
81
81
  if (value) {
82
82
  if (typeof value === "object") {
83
83
  if (Array.isArray(value) && typeof value[0] === "string") {
84
- result += ` tag ${tag}: ${JSON.stringify(value)}\n`;
84
+ result += ` tag ${tag}: ${JSON.stringify(value).split('","').join('", "')}\n`;
85
85
  }
86
86
  else {
87
87
  // Convert JSON Object to Ruby Hash
@@ -96,10 +96,14 @@ class Control {
96
96
  }
97
97
  }
98
98
  else if (typeof value === "string") {
99
- result += ` tag ${tag}: "${(0, global_1.wrapAndEscapeQuotes)((0, global_1.removeNewlinePlaceholders)(value), lineLength)}"\n`;
99
+ result += ` tag ${tag}: "${(0, global_1.escapeDoubleQuotes)((0, global_1.removeNewlinePlaceholders)(value))}"\n`;
100
100
  }
101
101
  }
102
102
  });
103
+ if (this.describe) {
104
+ result += '\n';
105
+ result += this.describe;
106
+ }
103
107
  result += "end";
104
108
  return result;
105
109
  }
@@ -45,7 +45,7 @@ function processProfileJSON(profileInput) {
45
45
  version: profileInput.data.version,
46
46
  });
47
47
  profileInput.data.controls.forEach((control) => {
48
- profile.controls.push(new control_1.default({
48
+ const newControl = new control_1.default({
49
49
  id: control.id,
50
50
  title: control.title,
51
51
  desc: control.desc,
@@ -53,7 +53,17 @@ function processProfileJSON(profileInput) {
53
53
  code: control.code,
54
54
  tags: control.tags,
55
55
  descs: (0, control_1.objectifyDescriptions)(control.descriptions),
56
- }));
56
+ });
57
+ // Migrate check and fix text from tags to descriptions
58
+ if (newControl.tags.check && !newControl.descs.check) {
59
+ lodash_1.default.set(newControl.descs, 'check', control.tags.check);
60
+ lodash_1.default.set(newControl.tags, 'check', undefined);
61
+ }
62
+ if (newControl.tags.fix && !newControl.descs.fix) {
63
+ lodash_1.default.set(newControl.descs, 'fix', control.tags.fix);
64
+ lodash_1.default.set(newControl.tags, 'fix', undefined);
65
+ }
66
+ profile.controls.push(newControl);
57
67
  });
58
68
  return profile;
59
69
  }
@@ -37,7 +37,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
37
37
  summary: parsedXML.Benchmark[0].description[0]['#text']
38
38
  });
39
39
  rules.forEach(rule => {
40
- var _a, _b, _c, _d;
40
+ var _a, _b, _c;
41
41
  let extractedDescription;
42
42
  if (Array.isArray(rule.description)) {
43
43
  extractedDescription = rule.description[0]['#text'];
@@ -68,29 +68,16 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
68
68
  default:
69
69
  throw new Error('useRuleId must be one of "group", "rule", or "version"');
70
70
  }
71
- if (removeNewlines) {
72
- const title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] ? ensureDecodedXMLStringValue(rule.title) : `[[[MISSING SEVERITY FROM STIG]]] ${ensureDecodedXMLStringValue(rule.title)}`);
73
- control.title = title.replace(/\n/g, '{{{{newlineHERE}}}}');
74
- const desc = (0, xccdf_1.removeXMLSpecialCharacters)(typeof extractedDescription === 'string' ? extractedDescription : ((_a = extractedDescription.VulnDiscussion) === null || _a === void 0 ? void 0 : _a.split('Satisfies: ')[0]) || 'Missing Description');
75
- control.desc = desc === null || desc === void 0 ? void 0 : desc.trim().replace(/\n/g, '{{{{newlineHERE}}}}');
76
- }
77
- else {
78
- control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] ? ensureDecodedXMLStringValue(rule.title) : `[[[MISSING SEVERITY FROM STIG]]] ${ensureDecodedXMLStringValue(rule.title)}`);
79
- control.desc = (0, xccdf_1.removeXMLSpecialCharacters)(typeof extractedDescription === 'string' ? extractedDescription : ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.split('Satisfies: ')[0]) || 'Missing Description');
80
- }
81
- control.impact = (0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'critical', rule.group['@_id']);
71
+ control.title = (0, xccdf_1.removeXMLSpecialCharacters)(rule['@_severity'] ? ensureDecodedXMLStringValue(rule.title) : `[[[MISSING SEVERITY FROM STIG]]] ${ensureDecodedXMLStringValue(rule.title)}`);
72
+ control.desc = (0, xccdf_1.removeXMLSpecialCharacters)(typeof extractedDescription === 'string' ? extractedDescription : ((_a = extractedDescription.VulnDiscussion) === null || _a === void 0 ? void 0 : _a.split('Satisfies: ')[0]) || 'Missing Description');
73
+ control.impact = (0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'medium', rule.group['@_id']);
82
74
  if (!control.descs || Array.isArray(control.descs)) {
83
75
  control.descs = {};
84
76
  }
85
77
  if (rule.check) {
86
78
  if (rule.check.some((ruleValue) => 'check-content' in ruleValue)) {
87
- if (removeNewlines) {
88
- const check = (0, xccdf_1.removeXMLSpecialCharacters)(rule.check ? rule.check[0]['check-content'] : 'Missing description');
89
- control.descs.check = check.replace(/\n/g, '{{{{newlineHERE}}}}');
90
- }
91
- else {
92
- control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(rule.check ? rule.check[0]['check-content'] : 'Missing description');
93
- }
79
+ control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(rule.check ? rule.check[0]['check-content'] : 'Missing description');
80
+ control.tags.check_id = rule.check[0]['@_system'];
94
81
  }
95
82
  else if (rule.check.some((ruleValue) => 'check-content-ref' in ruleValue) && ovalDefinitions) {
96
83
  let referenceID = null;
@@ -104,26 +91,14 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
104
91
  }
105
92
  }
106
93
  if (referenceID && referenceID in ovalDefinitions) {
107
- if (removeNewlines) {
108
- const check = (0, xccdf_1.removeXMLSpecialCharacters)(ovalDefinitions[referenceID].metadata[0].title);
109
- control.descs.check = check.replace(/\n/g, '{{{{newlineHERE}}}}');
110
- }
111
- else {
112
- control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(ovalDefinitions[referenceID].metadata[0].title);
113
- }
94
+ control.descs.check = (0, xccdf_1.removeXMLSpecialCharacters)(ovalDefinitions[referenceID].metadata[0].title);
114
95
  }
115
96
  else if (referenceID) {
116
97
  console.warn(`Could not find OVAL definition for ${referenceID}`);
117
98
  }
118
99
  }
119
100
  }
120
- if (removeNewlines) {
121
- const fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? rule.fix[0]['#text'] || 'Missing fix text' : 'Missing fix text'));
122
- control.descs.fix = fix.replace(/\n/g, '{{{{newlineHERE}}}}');
123
- }
124
- else {
125
- control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? rule.fix[0]['#text'] || 'Missing fix text' : 'Missing fix text'));
126
- }
101
+ control.descs.fix = (0, xccdf_1.removeXMLSpecialCharacters)(rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? rule.fix[0]['#text'] || 'Missing fix text' : 'Missing fix text'));
127
102
  control.tags.severity = (0, xccdf_1.impactNumberToSeverityString)((0, xccdf_1.severityStringToImpact)(rule['@_severity'] || 'critical', control.id || 'Unknown'));
128
103
  control.tags.gid = rule.group['@_id'],
129
104
  control.tags.rid = rule['@_id'];
@@ -141,7 +116,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
141
116
  control.tags.rationale = rule['rationale'][0]['#text'];
142
117
  }
143
118
  if (typeof extractedDescription === 'object') {
144
- control.tags.satisfies = ((_c = extractedDescription.VulnDiscussion) === null || _c === void 0 ? void 0 : _c.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined;
119
+ control.tags.satisfies = ((_b = extractedDescription.VulnDiscussion) === null || _b === void 0 ? void 0 : _b.includes('Satisfies: ')) && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined;
145
120
  control.tags.false_negatives = extractedDescription.FalseNegatives || undefined;
146
121
  control.tags.false_positives = extractedDescription.FalsePositives || undefined;
147
122
  control.tags.documentable = typeof extractedDescription.Documentable === 'boolean' ? extractedDescription.Documentable : undefined;
@@ -187,14 +162,9 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
187
162
  }
188
163
  (_c = control.tags.nist) === null || _c === void 0 ? void 0 : _c.push(identifier['#text']);
189
164
  }
190
- else {
191
- // console.log('Alert')
192
- // console.log(identifier['@_system'])
193
- // console.log(identifier['#text'])
194
- }
195
165
  });
196
166
  }
197
- (_d = rule.reference) === null || _d === void 0 ? void 0 : _d.forEach((reference) => {
167
+ (_c = rule.reference) === null || _c === void 0 ? void 0 : _c.forEach((reference) => {
198
168
  var _a, _b, _c, _d;
199
169
  if (lodash_1.default.get(reference, '@_href') === '') {
200
170
  (_a = control.refs) === null || _a === void 0 ? void 0 : _a.push(lodash_1.default.get(reference, '#text'));
@@ -1,3 +1,3 @@
1
1
  {
2
- "data": "## Automattic Update: {{fromVersion}} -> {{toVersion}}\n\n### New Controls:\n{{#addedControls}}\n+ {{id}} - {{title}}\n{{/addedControls}}\n\n### Updated Check/Fixes:\n#### Checks:\n\n{{#checks}}\n{{id}}:\n```\n{{{check}}}\n```\n\n\n{{/checks}}"
2
+ "data": "## Automatic Update: {{fromVersion}} -> {{toVersion}}\n\n### New Controls:\n{{#addedControls}}\n+ {{id}} - {{title}}\n{{/addedControls}}\n\n{{#hasRenamedControls}}\n### Updated Control IDs:\n<details>\n <summary>Click to expand.</summary>\n \n {{#renamedControls}}\n - {{oldId}} -> {{newId}}\n {{/renamedControls}} \n</details>\n{{/hasRenamedControls}}\n\n### Updated Check/Fixes:\n#### Checks:\n<details open>\n <summary>Click to expand.</summary>\n{{#updatedChecks}}\n{{id}}:\nOld: \n```\n{{{old}}}\n\n```\n\nUpdated:\n```\n{{{new}}}\n\n```\n---\n{{/updatedChecks}}\n</details>\n\n#### Fixes:\n<details open>\n <summary>Click to expand.</summary>\n{{#updatedFixes}}\n{{id}}:\nOld: \n```\n{{{old}}}\n\n```\nNew:\n```\n{{{new}}}\n\n```\n---\n{{/updatedFixes}}\n</details>\n\n### Updated Impacts\n<details open>\n <summary>Click to expand.</summary>\n{{#updatedImpacts}}\n{{id}}:\nOld: {{old}}\nNew: {{new}}\n---\n{{/updatedImpacts}}\n</details>\n\n### Updated Titles\n<details>\n <summary>Click to expand.</summary>\n{{#updatedTitles}}\n{{id}}:\nOld: {{old}}\nNew: {{new}}\n---\n{{/updatedTitles}}\n</details>\n\n### Updated Descriptions\n<details>\n <summary>Click to expand.</summary>\n{{#updatedDescriptions}}\n{{id}}:\nOld:\n```\n{{{old}}}\n\n```\nNew:\n```\n{{{new}}}\n\n```\n---\n{{/updatedDescriptions}}\n</details>"
3
3
  }
@@ -1,8 +1,9 @@
1
- import Profile from '../objects/profile';
2
- import { ProfileDiff } from '../types/diff';
1
+ import Profile from "../objects/profile";
2
+ import { ProfileDiff } from "../types/diff";
3
+ import winston from "winston";
3
4
  export declare function removeNewlines(control?: Record<string, unknown>): Record<string, unknown>;
4
5
  export declare function simplifyDiff(diffData: Record<string, unknown>): Record<string, unknown>;
5
- export declare function diffProfile(fromProfile: Profile, toProfile: Profile): {
6
+ export declare function diffProfile(fromProfile: Profile, toProfile: Profile, logger: winston.Logger): {
6
7
  simplified: ProfileDiff;
7
8
  originalDiff: Record<string, unknown>;
8
9
  };
@@ -4,15 +4,16 @@ exports.diffProfile = exports.simplifyDiff = exports.removeNewlines = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const json_diff_1 = require("json-diff");
6
6
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
+ const update_1 = require("./update");
7
8
  function removeNewlines(control) {
8
9
  if (!control) {
9
10
  return {};
10
11
  }
11
12
  return lodash_1.default.mapValues(control, (value) => {
12
- if (typeof value === 'string') {
13
- return value.replace(/\n/g, '{{{{newlineHERE}}}}').trim();
13
+ if (typeof value === "string") {
14
+ return value.replace(/\n/g, "{{{{newlineHERE}}}}").trim();
14
15
  }
15
- else if (typeof value === 'object' && value !== null) {
16
+ else if (typeof value === "object" && value !== null) {
16
17
  return removeNewlines(value);
17
18
  }
18
19
  return value;
@@ -21,24 +22,28 @@ function removeNewlines(control) {
21
22
  exports.removeNewlines = removeNewlines;
22
23
  function simplifyDiff(diffData) {
23
24
  return lodash_1.default.transform(diffData, (result, diffValue, key) => {
24
- if (lodash_1.default.has(diffValue, '__new')) {
25
+ if (lodash_1.default.has(diffValue, "__new")) {
25
26
  // Remove any trailing space
26
- if (typeof lodash_1.default.get(diffValue, '__new') === 'string' && typeof lodash_1.default.get(diffValue, '__old') === 'string') {
27
- if (lodash_1.default.get(diffValue, '__new').trim() !== lodash_1.default.get(diffValue, '__old').trim()) {
28
- lodash_1.default.set(result, key, lodash_1.default.get(diffValue, '__new'));
27
+ if (typeof lodash_1.default.get(diffValue, "__new") === "string" &&
28
+ typeof lodash_1.default.get(diffValue, "__old") === "string") {
29
+ if (lodash_1.default.get(diffValue, "__new").trim() !==
30
+ lodash_1.default.get(diffValue, "__old").trim()) {
31
+ lodash_1.default.set(result, key, lodash_1.default.get(diffValue, "__new"));
29
32
  }
30
33
  }
31
34
  else {
32
- result[key] = lodash_1.default.get(diffValue, '__new');
35
+ result[key] = lodash_1.default.get(diffValue, "__new");
33
36
  }
34
37
  }
35
38
  else if (Array.isArray(diffValue)) {
36
- result[key] = diffValue.map((value) => value[0] === '+' && value[1]).filter(value => value);
39
+ result[key] = diffValue
40
+ .map((value) => value[0] === "+" && value[1])
41
+ .filter((value) => value);
37
42
  }
38
- else if (typeof diffValue === 'object') {
43
+ else if (typeof diffValue === "object") {
39
44
  result[key] = simplifyDiff(diffValue);
40
45
  }
41
- else if (key.endsWith('__deleted')) {
46
+ else if (key.endsWith("__deleted")) {
42
47
  return undefined;
43
48
  }
44
49
  else {
@@ -47,37 +52,62 @@ function simplifyDiff(diffData) {
47
52
  });
48
53
  }
49
54
  exports.simplifyDiff = simplifyDiff;
50
- function diffProfile(fromProfile, toProfile) {
55
+ function diffProfile(fromProfile, toProfile, logger) {
51
56
  const profileDiff = {
52
57
  addedControlIDs: [],
53
58
  removedControlIDs: [],
59
+ renamedControlIds: {},
54
60
  addedControls: {},
55
- changedControls: {}
61
+ changedControls: {},
56
62
  };
57
63
  const originalDiff = {
58
64
  addedControlIDs: [],
59
65
  removedControlIDs: [],
66
+ renamedControlIds: {},
60
67
  addedControls: {},
61
- changedControls: {}
68
+ changedControls: {},
62
69
  };
63
- const fromControlIDs = fromProfile.controls.map((control) => control.id).sort();
70
+ const fromControlIDs = fromProfile.controls
71
+ .map((control) => control.id)
72
+ .sort();
64
73
  const toControlIDs = toProfile.controls.map((control) => control.id).sort();
65
74
  // Find new controls
66
75
  const controlIDDiff = (0, json_diff_1.diff)(fromControlIDs, toControlIDs);
76
+ // Contains the new IDs
77
+ const changedControlIds = [];
67
78
  controlIDDiff === null || controlIDDiff === void 0 ? void 0 : controlIDDiff.forEach((diffValue) => {
68
- if (diffValue[0] === '-') {
69
- profileDiff.removedControlIDs.push(diffValue[1]);
70
- originalDiff.removedControlIDs.push(diffValue[1]);
79
+ if (diffValue[0] === "-") {
80
+ const existingControl = fromProfile.controls.find((control) => control.id === diffValue[1]);
81
+ // Check if the control has been given a new ID
82
+ if (existingControl) {
83
+ const newControl = (0, update_1.findUpdatedControlByAllIdentifiers)(existingControl, toProfile.controls);
84
+ if (newControl && newControl.id !== existingControl.id) {
85
+ profileDiff.renamedControlIds[existingControl.id] = newControl.id;
86
+ originalDiff.renamedControlIds[existingControl.id] = newControl.id;
87
+ changedControlIds.push(newControl.id.toLowerCase());
88
+ const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(existingControl, newControl), "code__deleted");
89
+ profileDiff.changedControls[newControl.id] = simplifyDiff(controlDiff);
90
+ originalDiff.changedControls[newControl.id] = controlDiff;
91
+ logger.verbose(`Control ${existingControl.id} has been upgraded to ${newControl.id}`);
92
+ }
93
+ else {
94
+ profileDiff.removedControlIDs.push(diffValue[1]);
95
+ originalDiff.removedControlIDs.push(diffValue[1]);
96
+ }
97
+ }
98
+ else {
99
+ logger.error(`Unable to find existing control ${diffValue[1]}`);
100
+ }
71
101
  }
72
- else if (diffValue[0] === '+') {
102
+ else if (diffValue[0] === "+" && !changedControlIds.includes(diffValue[1].toLowerCase())) {
73
103
  profileDiff.addedControlIDs.push(diffValue[1]);
74
104
  originalDiff.addedControlIDs.push(diffValue[1]);
75
105
  }
76
106
  });
77
- // Add new controls to changedControls
107
+ // Add new controls to addedControls
78
108
  profileDiff.addedControlIDs.forEach((addedControl) => {
79
109
  const newControl = toProfile.controls.find((control) => addedControl === control.id);
80
- if (newControl) {
110
+ if (newControl && !profileDiff.changedControls[newControl.id]) {
81
111
  profileDiff.addedControls[addedControl] = newControl;
82
112
  originalDiff.addedControls[addedControl] = newControl;
83
113
  }
@@ -86,7 +116,7 @@ function diffProfile(fromProfile, toProfile) {
86
116
  for (const fromControl of fromProfile.controls) {
87
117
  const toControl = toProfile.controls.find((control) => control.id === fromControl.id);
88
118
  if (toControl) {
89
- const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(fromControl, toControl), 'code__deleted');
119
+ const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(fromControl, toControl), "code__deleted");
90
120
  if (controlDiff) {
91
121
  profileDiff.changedControls[toControl.id] = simplifyDiff(controlDiff);
92
122
  originalDiff.changedControls[toControl.id] = controlDiff;
@@ -1,5 +1,5 @@
1
1
  import { ProfileDiff } from "../types/diff";
2
- import Profile from '../objects/profile';
2
+ import Profile from "../objects/profile";
3
3
  export declare function createDiffMarkdown(diff: {
4
4
  simplified: ProfileDiff;
5
5
  originalDiff: any;
@@ -4,26 +4,87 @@ exports.createDiffMarkdown = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const mustache_1 = tslib_1.__importDefault(require("mustache"));
6
6
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
- const global_1 = require("./global");
8
- const xccdf_1 = require("./xccdf");
9
7
  const automatticUpdateTemplate_json_1 = tslib_1.__importDefault(require("../resources/automatticUpdateTemplate.json"));
10
8
  function getUpdatedCheckForId(id, profile) {
11
9
  const foundControl = profile.controls.find((control) => control.id === id);
12
- return lodash_1.default.get(foundControl === null || foundControl === void 0 ? void 0 : foundControl.descs, 'check') || 'Missing check';
10
+ return lodash_1.default.get(foundControl === null || foundControl === void 0 ? void 0 : foundControl.descs, "check") || "Missing check";
13
11
  }
14
12
  function createDiffMarkdown(diff, updatedProfile) {
15
13
  const renderableDiffData = {
16
14
  addedControls: Object.values(diff.simplified.addedControls),
17
- checks: [],
18
- fixes: [],
15
+ hasRenamedControls: false,
16
+ renamedControls: [],
17
+ updatedChecks: [],
18
+ updatedFixes: [],
19
+ updatedImpacts: [],
20
+ updatedTitles: [],
21
+ updatedDescriptions: [],
19
22
  };
20
- Object.entries(diff.simplified.changedControls).forEach(([id, updatedControl]) => {
21
- if (lodash_1.default.get(updatedControl, "descs.check")) {
22
- // console.log(removeXMLSpecialCharacters(removeNewlinePlaceholders(getUpdatedCheckForId(id, updatedProfile))))
23
- renderableDiffData.checks.push({
24
- id,
25
- check: (0, xccdf_1.removeXMLSpecialCharacters)((0, global_1.removeNewlinePlaceholders)(getUpdatedCheckForId(id, updatedProfile))),
26
- });
23
+ Object.entries(diff.simplified.renamedControlIds).forEach(([oldId, newId]) => {
24
+ renderableDiffData.hasRenamedControls = true;
25
+ renderableDiffData.renamedControls.push({
26
+ oldId: oldId,
27
+ newId: newId,
28
+ });
29
+ });
30
+ Object.entries(diff.originalDiff.changedControls).forEach(([id, controlDiff]) => {
31
+ var _a, _b;
32
+ if ((_a = controlDiff.descs) === null || _a === void 0 ? void 0 : _a.check) {
33
+ const oldCheck = lodash_1.default.get(controlDiff.descs.check, "__old");
34
+ const newCheck = lodash_1.default.get(controlDiff.descs.check, "__new");
35
+ if (oldCheck.replace(/\n/g, "").replace(/\W/g, "") !==
36
+ newCheck.replace(/\n/g, "").replace(/\W/g, "")) {
37
+ renderableDiffData.updatedChecks.push({
38
+ id: id,
39
+ old: oldCheck,
40
+ new: newCheck,
41
+ });
42
+ }
43
+ }
44
+ if ((_b = controlDiff.descs) === null || _b === void 0 ? void 0 : _b.fix) {
45
+ const oldFix = lodash_1.default.get(controlDiff.descs.fix, "__old");
46
+ const newFix = lodash_1.default.get(controlDiff.descs.fix, "__new");
47
+ if (oldFix.replace(/\n/g, "").replace(/\W/g, "") !==
48
+ newFix.replace(/\n/g, "").replace(/\W/g, "")) {
49
+ renderableDiffData.updatedFixes.push({
50
+ id: id,
51
+ old: oldFix,
52
+ new: newFix,
53
+ });
54
+ }
55
+ }
56
+ if (controlDiff.impact) {
57
+ const oldImpact = lodash_1.default.get(controlDiff.impact, "__old");
58
+ const newImpact = lodash_1.default.get(controlDiff.impact, "__new");
59
+ if (oldImpact !== newImpact) {
60
+ renderableDiffData.updatedImpacts.push({
61
+ id: id,
62
+ old: oldImpact,
63
+ new: newImpact,
64
+ });
65
+ }
66
+ }
67
+ if (controlDiff.title) {
68
+ const oldTitle = lodash_1.default.get(controlDiff.title, "__old");
69
+ const newTitle = lodash_1.default.get(controlDiff.title, "__new");
70
+ if (oldTitle !== newTitle) {
71
+ renderableDiffData.updatedTitles.push({
72
+ id: id,
73
+ old: oldTitle,
74
+ new: newTitle,
75
+ });
76
+ }
77
+ }
78
+ if (controlDiff.desc) {
79
+ const oldDesc = lodash_1.default.get(controlDiff.desc, "__old");
80
+ const newDesc = lodash_1.default.get(controlDiff.desc, "__new");
81
+ if (oldDesc !== newDesc) {
82
+ renderableDiffData.updatedDescriptions.push({
83
+ id: id,
84
+ old: oldDesc,
85
+ new: newDesc,
86
+ });
87
+ }
27
88
  }
28
89
  });
29
90
  // Render output
@@ -1,2 +1,2 @@
1
1
  import winston from "winston";
2
- export declare function createWinstonLogger(mapperName: string, level?: string): winston.Logger;
2
+ export declare function createWinstonLogger(level?: string): winston.Logger;
@@ -3,13 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWinstonLogger = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const winston_1 = tslib_1.__importDefault(require("winston"));
6
- function createWinstonLogger(mapperName, level = 'debug') {
6
+ function createWinstonLogger(level = 'debug') {
7
7
  return winston_1.default.createLogger({
8
8
  transports: [new winston_1.default.transports.Console()],
9
9
  level: level,
10
10
  format: winston_1.default.format.combine(winston_1.default.format.timestamp({
11
11
  format: 'MMM-DD-YYYY HH:mm:ss Z',
12
- }), winston_1.default.format.printf(info => `[${[info.timestamp]}] ${mapperName} ${info.message}`)),
12
+ }), winston_1.default.format.printf(info => `[${[info.timestamp]}] ${info.message}`)),
13
13
  });
14
14
  }
15
15
  exports.createWinstonLogger = createWinstonLogger;
@@ -11,6 +11,7 @@ export declare type UpdatedProfileReturn = {
11
11
  };
12
12
  markdown: string;
13
13
  };
14
- export declare function updateControl(from: Control, update: Partial<Control>): Control;
15
- export declare function updateProfile(from: Profile, using: Profile): Omit<UpdatedProfileReturn, 'markdown'>;
14
+ export declare function findUpdatedControlByAllIdentifiers(existingControl: Control, updatedControls: Control[]): Control | undefined;
15
+ export declare function updateControl(from: Control, update: Partial<Control>, logger: winston.Logger): Control;
16
+ export declare function updateProfile(from: Profile, using: Profile, logger: winston.Logger): Omit<UpdatedProfileReturn, 'markdown'>;
16
17
  export declare function updateProfileUsingXCCDF(from: Profile, using: string, id: 'group' | 'rule' | 'version', logger: winston.Logger, ovalDefinitions?: Record<string, OvalDefinitionValue>): UpdatedProfileReturn;
@@ -2,13 +2,14 @@
2
2
  // Utilities to update a profile or control with new metadata
3
3
  // The ultimate goal is to preserve all the metadata that is already there and only add what is new
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.updateProfileUsingXCCDF = exports.updateProfile = exports.updateControl = void 0;
5
+ exports.updateProfileUsingXCCDF = exports.updateProfile = exports.updateControl = exports.findUpdatedControlByAllIdentifiers = void 0;
6
6
  const tslib_1 = require("tslib");
7
7
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
8
8
  const profile_1 = tslib_1.__importDefault(require("../objects/profile"));
9
9
  const xccdf_1 = require("../parsers/xccdf");
10
10
  const diff_1 = require("./diff");
11
11
  const diffMarkdown_1 = require("./diffMarkdown");
12
+ const knownInSpecKeywords = ['title', 'desc', 'impact', 'ref', 'tag', "\""];
12
13
  function projectValuesOntoExistingObj(dst, src, currentPath = '') {
13
14
  for (const updatedValue in src) {
14
15
  const existingValue = lodash_1.default.get(dst, updatedValue);
@@ -31,40 +32,97 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
31
32
  }
32
33
  return dst;
33
34
  }
34
- function updateControl(from, update) {
35
- return projectValuesOntoExistingObj(from, update);
35
+ // This is the most likely thing to break if you are getting code formatting issues.
36
+ // Extract the existing describe blocks (what is actually run by inspec for validation)
37
+ function getExistingDescribeFromControl(control) {
38
+ if (control.code) {
39
+ let existingDescribeBlock = '';
40
+ let inQuoteBlock = false;
41
+ control.code.split('\n').forEach((line) => {
42
+ const wordSplit = line.trim().split(' ');
43
+ wordSplit.forEach((word, index) => {
44
+ const charSplit = word.split('');
45
+ charSplit.forEach((char, index) => {
46
+ if (char === '"' && charSplit[index - 1] !== '\\') {
47
+ inQuoteBlock = !inQuoteBlock;
48
+ }
49
+ });
50
+ });
51
+ if (!inQuoteBlock) {
52
+ // Get the number of spaces at the beggining of the current line
53
+ const spaces = line.substring(0, line.indexOf(wordSplit[0])).length;
54
+ if (spaces >= 2) {
55
+ const firstWord = wordSplit[0];
56
+ if (knownInSpecKeywords.indexOf(firstWord.toLowerCase()) === -1 || (knownInSpecKeywords.indexOf(firstWord.toLowerCase()) !== -1 && spaces > 2)) {
57
+ existingDescribeBlock += line + '\n';
58
+ }
59
+ }
60
+ }
61
+ });
62
+ return existingDescribeBlock;
63
+ }
64
+ else {
65
+ return '';
66
+ }
67
+ }
68
+ function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
69
+ // Try to match based on IDs
70
+ let updatedControl = updatedControls.find((updatedControl) => {
71
+ return updatedControl.id.toLowerCase() === existingControl.id.toLowerCase();
72
+ });
73
+ if (updatedControl) {
74
+ return updatedControl;
75
+ }
76
+ // Try to match based on legacy identifiers
77
+ updatedControl = updatedControls.find((updatedControl) => {
78
+ var _a;
79
+ return (_a = updatedControl.tags.legacy) === null || _a === void 0 ? void 0 : _a.some((legacyTag) => {
80
+ var _a;
81
+ return legacyTag.toLowerCase() === ((_a = existingControl.id) === null || _a === void 0 ? void 0 : _a.toLowerCase());
82
+ });
83
+ });
84
+ if (updatedControl) {
85
+ return updatedControl;
86
+ }
87
+ return undefined;
88
+ }
89
+ exports.findUpdatedControlByAllIdentifiers = findUpdatedControlByAllIdentifiers;
90
+ function updateControl(from, update, logger) {
91
+ const existingDescribeBlock = getExistingDescribeFromControl(from);
92
+ logger.debug(`Existing describe block for control ${from.id}: ${JSON.stringify(existingDescribeBlock)}`);
93
+ const projectedControl = projectValuesOntoExistingObj(from, update);
94
+ projectedControl.describe = existingDescribeBlock;
95
+ return projectedControl;
36
96
  }
37
97
  exports.updateControl = updateControl;
38
- function updateProfile(from, using) {
98
+ function updateProfile(from, using, logger) {
39
99
  // Update the profile with the new metadata
40
100
  const to = new profile_1.default(lodash_1.default.omit(from, 'controls'));
41
101
  // Find the diff
42
- const diff = (0, diff_1.diffProfile)(from, using);
102
+ const diff = (0, diff_1.diffProfile)(from, using, logger);
43
103
  // Add the new controls
44
104
  diff.simplified.addedControlIDs.forEach(id => {
45
105
  const addedControl = diff.simplified.addedControls[id];
46
106
  if (addedControl) {
107
+ logger.debug(`New Control: ${addedControl.id} - ${addedControl.title}`);
47
108
  to.controls.push(addedControl);
48
109
  }
49
110
  else {
50
- throw new Error("New control added but don't have the control data");
111
+ throw new Error(`New control ${id} added but don't have the control data`);
51
112
  }
52
113
  });
53
114
  // Update the existing controls
54
115
  for (const existingControl of from.controls) {
55
- const updatedControl = using.controls.find(control => control.id === existingControl.id);
116
+ const updatedControl = findUpdatedControlByAllIdentifiers(existingControl, using.controls);
56
117
  if (updatedControl) {
57
- const controlDiff = diff.simplified.changedControls[existingControl.id];
118
+ const controlDiff = diff.simplified.changedControls[updatedControl.id];
58
119
  if (controlDiff) {
59
- to.controls.push(updateControl(existingControl, controlDiff));
120
+ to.controls.push(updateControl(existingControl, controlDiff, logger));
60
121
  }
61
122
  else {
62
123
  to.controls.push(existingControl);
63
124
  }
64
125
  }
65
- else {
66
- console.log("Control not updated: " + existingControl.id);
67
- }
68
126
  }
69
127
  return {
70
128
  profile: to,
@@ -73,7 +131,7 @@ function updateProfile(from, using) {
73
131
  }
74
132
  exports.updateProfile = updateProfile;
75
133
  function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
76
- console.log(from.controls[0].desc);
134
+ logger.debug(`Updating profile ${from.name} with control IDs: ${id}`);
77
135
  // Parse the XCCDF benchmark and convert it into a Profile
78
136
  logger.debug('Loading XCCDF File');
79
137
  const xccdfProfile = (0, xccdf_1.processXCCDF)(using, false, id);
@@ -83,7 +141,7 @@ function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
83
141
  logger.debug('Loaded XCCDF File with newline replacements');
84
142
  // Update the profile and return
85
143
  logger.debug('Creating updated profile');
86
- const updatedProfile = updateProfile(from, xccdfProfile);
144
+ const updatedProfile = updateProfile(from, xccdfProfile, logger);
87
145
  logger.debug('Creating diff markdown');
88
146
  // Create the markdown
89
147
  const markdown = (0, diffMarkdown_1.createDiffMarkdown)(updatedProfile.diff, xccdfProfileWithNLReplacement);
@@ -6,20 +6,6 @@ const fast_xml_parser_1 = tslib_1.__importDefault(require("fast-xml-parser"));
6
6
  const htmlparser = tslib_1.__importStar(require("htmlparser2"));
7
7
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
8
8
  const he_1 = tslib_1.__importDefault(require("he"));
9
- var htmlEntities = {
10
- nbsp: ' ',
11
- cent: '¢',
12
- pound: '£',
13
- yen: '¥',
14
- euro: '€',
15
- copy: '©',
16
- reg: '®',
17
- lt: '<',
18
- gt: '>',
19
- quot: '"',
20
- amp: '&',
21
- apos: '\''
22
- };
23
9
  function convertEncodedXmlIntoJson(encodedXml) {
24
10
  return fast_xml_parser_1.default.parse(encodedXml, {
25
11
  ignoreAttributes: false,
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mitre/inspec-objects",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@mitre/inspec-objects",
9
- "version": "0.0.13",
9
+ "version": "0.0.16",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@types/flat": "^5.0.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/inspec-objects",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "description": "Typescript objects for normalizing between InSpec profiles and XCCDF benchmarks",
5
5
  "main": "lib/index.js",
6
6
  "publishConfig": {