@mitre/hdf-converters 2.11.5 → 2.12.0

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.
@@ -1,16 +1,65 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
27
  };
5
28
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SonarQubeMapper = exports.SonarQubeResults = void 0;
29
+ exports.SonarqubeResults = exports.SonarqubeMapper = void 0;
7
30
  const axios_1 = __importDefault(require("axios"));
31
+ const _ = __importStar(require("lodash"));
32
+ const semver_1 = require("semver");
8
33
  const inspecjs_1 = require("inspecjs");
9
34
  const package_json_1 = require("../package.json");
10
35
  const base_converter_1 = require("./base-converter");
11
36
  const CweNistMapping_1 = require("./mappings/CweNistMapping");
12
37
  const OwaspNistMapping_1 = require("./mappings/OwaspNistMapping");
13
38
  const global_1 = require("./utils/global");
39
+ const logger = (0, global_1.createWinstonLogger)('SonarQube2HDF');
40
+ var SonarqubeVersion;
41
+ (function (SonarqubeVersion) {
42
+ SonarqubeVersion["Eight"] = "8.0.0";
43
+ SonarqubeVersion["Nine"] = "9.0.0";
44
+ SonarqubeVersion["Ten"] = "10.0.0";
45
+ SonarqubeVersion["Twenty_five"] = "25.0.0";
46
+ })(SonarqubeVersion || (SonarqubeVersion = {}));
47
+ function isSonarqubeVersionEight(version) {
48
+ const nextHigherVersion = SonarqubeVersion.Nine;
49
+ return (0, semver_1.lt)((0, semver_1.coerce)(version) || nextHigherVersion, nextHigherVersion);
50
+ }
51
+ function isSonarqubeVersionNine(version) {
52
+ const nextHigherVersion = SonarqubeVersion.Ten;
53
+ return (0, semver_1.lt)((0, semver_1.coerce)(version) || nextHigherVersion, nextHigherVersion);
54
+ }
55
+ function isSonarqubeVersionTen(version) {
56
+ const nextHigherVersion = SonarqubeVersion.Twenty_five;
57
+ return (0, semver_1.lt)((0, semver_1.coerce)(version) || nextHigherVersion, nextHigherVersion);
58
+ }
59
+ function isSonarqubeVersionTwenty_five(version) {
60
+ const nextHigherVersion = '26.0.0';
61
+ return (0, semver_1.lt)((0, semver_1.coerce)(version) || nextHigherVersion, nextHigherVersion);
62
+ }
14
63
  const IMPACT_MAPPING = new Map([
15
64
  ['blocker', 1.0],
16
65
  ['critical', 0.7],
@@ -20,62 +69,347 @@ const IMPACT_MAPPING = new Map([
20
69
  ]);
21
70
  const CWE_NIST_MAPPING = new CweNistMapping_1.CweNistMapping();
22
71
  const OWASP_NIST_MAPPING = new OwaspNistMapping_1.OwaspNistMapping();
23
- function formatCodeDesc(vulnerability) {
24
- const typedVulnerability = vulnerability;
25
- if (typedVulnerability.textRange) {
26
- const snipHtml = `StartLine: ${typedVulnerability.textRange.startLine}, EndLine: ${typedVulnerability.textRange.endLine}<br>Code:<pre>${typedVulnerability.snip}</pre>`;
27
- return `Path:${typedVulnerability.component}:${typedVulnerability.textRange.startLine}:${typedVulnerability.textRange.endLine} ${snipHtml}`;
72
+ function parseOwaspInSysTags(issue) {
73
+ return issue.ruleInformation.rule.sysTags
74
+ .filter((s) => s.toLowerCase().startsWith('owasp-'))
75
+ .map((t) => t.substring('owasp-'.length).toUpperCase());
76
+ }
77
+ function parseOwaspTags(issue) {
78
+ let searchSpace = '';
79
+ const rule = issue.ruleInformation.rule;
80
+ if ('htmlDesc' in rule) {
81
+ searchSpace += rule.htmlDesc;
82
+ }
83
+ if (rule.descriptionSections) {
84
+ searchSpace += rule.descriptionSections.map((s) => s.content).join('');
28
85
  }
29
- else {
30
- return '';
86
+ const searchSpaceMatches = [
87
+ ...searchSpace.matchAll(/> ?OWASP.*?(Top .*?A\d\d?)/gu)
88
+ ].map((m) => m[1]);
89
+ const sysTagMatches = parseOwaspInSysTags(issue);
90
+ const totalMatches = searchSpaceMatches.concat(sysTagMatches);
91
+ if (totalMatches.length) {
92
+ return totalMatches;
31
93
  }
94
+ return undefined;
95
+ }
96
+ function parseCweTags(issue) {
97
+ let searchSpace = '';
98
+ const rule = issue.ruleInformation.rule;
99
+ if ('htmlDesc' in rule) {
100
+ searchSpace += rule.htmlDesc;
101
+ }
102
+ if (rule.descriptionSections) {
103
+ searchSpace += rule.descriptionSections.map((s) => s.content).join('');
104
+ }
105
+ const uniqueCwes = _.uniq(searchSpace.match(/CWE-\d\d\d?\d?\d?\d?\d/gi));
106
+ if (uniqueCwes.length) {
107
+ return uniqueCwes;
108
+ }
109
+ return undefined;
32
110
  }
33
111
  function parseNistTags(issue) {
34
112
  var _a, _b;
35
- const tags = [];
36
- (_a = issue.sysTags) === null || _a === void 0 ? void 0 : _a.forEach((sysTag) => {
37
- if (sysTag.toLowerCase().startsWith('owasp-')) {
38
- const identifier = [
39
- sysTag.toLowerCase().replace('owasp-', '').toUpperCase()
40
- ];
41
- tags.push(...OWASP_NIST_MAPPING.nistFilterNoDefault(identifier));
42
- }
43
- });
44
- (_b = issue.summary.match(/CWE-\d\d\d?\d?\d?\d?\d/gi)) === null || _b === void 0 ? void 0 : _b.forEach((match) => {
45
- tags.push(...CWE_NIST_MAPPING.nistFilter(match.split('-')[1]));
46
- });
47
- return tags;
113
+ const uniqueNist = _.uniq(((_a = parseCweTags(issue)) !== null && _a !== void 0 ? _a : [])
114
+ .flatMap((t) => CWE_NIST_MAPPING.nistFilter(t.split('-')[1]))
115
+ .concat(((_b = parseOwaspInSysTags(issue)) !== null && _b !== void 0 ? _b : []).flatMap((t) => OWASP_NIST_MAPPING.nistFilterNoDefault(t))));
116
+ if (uniqueNist.length) {
117
+ return uniqueNist;
118
+ }
119
+ return ['SA-11'];
48
120
  }
49
- class SonarQubeResults {
50
- constructor(sonarQubeHost, projectId, userToken, branchName, pullRequestID) {
51
- this.data = {
52
- issues: []
121
+ class SonarqubeMapper extends base_converter_1.BaseConverter {
122
+ constructor(data, withRaw = false) {
123
+ super(data);
124
+ this.data = data;
125
+ this.mappings = {
126
+ platform: {
127
+ name: 'Heimdall Tools',
128
+ release: package_json_1.version
129
+ },
130
+ version: package_json_1.version,
131
+ statistics: {},
132
+ profiles: [
133
+ {
134
+ name: 'SonarQube Scan',
135
+ version: {
136
+ transformer: (data) => `SonarQube v${data.sonarqubeVersion}`
137
+ },
138
+ title: {
139
+ transformer: (data) => {
140
+ const branch = data.branchName ? ` branch ${data.branchName}` : '';
141
+ const pullrequest = data.pullRequestID
142
+ ? ` pull request ${data.pullRequestID}`
143
+ : '';
144
+ const org = data.organization
145
+ ? ` organization ${data.organization}`
146
+ : '';
147
+ return `SonarQube Scan of project ${data.projectKey} on ${data.sonarqubeHost} at ${new Date().toISOString()}${data.branchName || data.pullRequestID || data.organization ? ' using' : ''}${[branch, pullrequest, org].filter((s) => s).join(',')}`;
148
+ }
149
+ },
150
+ supports: [],
151
+ attributes: [],
152
+ groups: [],
153
+ status: 'loaded',
154
+ controls: [
155
+ {
156
+ path: 'search.issues',
157
+ key: 'id',
158
+ desc: {
159
+ transformer: (issue) => {
160
+ const rule = issue.ruleInformation.rule;
161
+ if ('htmlDesc' in rule) {
162
+ return rule.htmlDesc;
163
+ }
164
+ if (!rule.descriptionSections) {
165
+ return '';
166
+ }
167
+ const def = rule.descriptionSections.find((d) => d.key === 'default');
168
+ if (def) {
169
+ return def.content;
170
+ }
171
+ const introduction = rule.descriptionSections.find((d) => d.key === 'introduction');
172
+ const rootcause = rule.descriptionSections.find((d) => d.key === 'root_cause');
173
+ return [introduction, rootcause]
174
+ .filter((s) => s !== undefined)
175
+ .map((s) => s.content)
176
+ .join('\n');
177
+ }
178
+ },
179
+ refs: [],
180
+ source_location: {},
181
+ id: { path: 'rule' },
182
+ title: { path: 'ruleInformation.rule.name' },
183
+ impact: {
184
+ path: 'severity',
185
+ transformer: (0, base_converter_1.impactMapping)(IMPACT_MAPPING)
186
+ },
187
+ tags: {
188
+ cci: {
189
+ transformer: (issue) => { var _a; return (0, global_1.getCCIsForNISTTags)((_a = parseNistTags(issue)) !== null && _a !== void 0 ? _a : []); }
190
+ },
191
+ nist: { transformer: parseNistTags },
192
+ cweid: { transformer: parseCweTags },
193
+ owasp: { transformer: parseOwaspTags },
194
+ createdAt: { path: 'ruleInformation.rule.createdAt' },
195
+ debtRemFnType: { path: 'ruleInformation.rule.debtRemFnType' },
196
+ defaultDebtRemFnType: {
197
+ path: 'ruleInformation.rule.defaultDebtRemFnType'
198
+ },
199
+ isExternal: { path: 'ruleInformation.rule.isExternal' },
200
+ isTemplate: { path: 'ruleInformation.rule.isTemplate' },
201
+ langName: { path: 'ruleInformation.rule.langName' },
202
+ remFnBaseEffort: { path: 'ruleInformation.rule.remFnBaseEffort' },
203
+ remFnOverloaded: { path: 'ruleInformation.rule.remFnOverloaded' },
204
+ remFnType: { path: 'ruleInformation.rule.remFnType' },
205
+ repo: { path: 'ruleInformation.rule.repo' },
206
+ scope: { path: 'ruleInformation.rule.scope' },
207
+ ruleSeverity: { path: 'ruleInformation.rule.severity' },
208
+ status: { path: 'ruleInformation.rule.status' },
209
+ transformer: (issue) => {
210
+ var _a, _b, _c, _d, _e, _f, _g, _h;
211
+ return ({
212
+ ...(0, global_1.conditionallyProvideAttribute)('Actives', issue.ruleInformation.actives, issue.ruleInformation.actives.length !== 0),
213
+ ...(0, global_1.conditionallyProvideAttribute)('Clean Code Attribute', issue.ruleInformation.rule.cleanCodeAttribute, ((_a = issue.ruleInformation.rule.cleanCodeAttribute) === null || _a === void 0 ? void 0 : _a.length) !== 0),
214
+ ...(0, global_1.conditionallyProvideAttribute)('Clean Code Attribute Category', issue.ruleInformation.rule.cleanCodeAttributeCategory, ((_b = issue.ruleInformation.rule.cleanCodeAttributeCategory) === null || _b === void 0 ? void 0 : _b.length) !== 0),
215
+ ...(0, global_1.conditionallyProvideAttribute)('Debt Overloaded', 'debtOverloaded' in issue.ruleInformation.rule &&
216
+ issue.ruleInformation.rule.debtOverloaded, 'debtOverloaded' in issue.ruleInformation.rule &&
217
+ issue.ruleInformation.rule.debtOverloaded !== undefined),
218
+ ...(0, global_1.conditionallyProvideAttribute)('Debt Rem Fn Coeff', 'debtRemFnCoeff' in issue.ruleInformation.rule &&
219
+ issue.ruleInformation.rule.debtRemFnCoeff, 'debtRemFnCoeff' in issue.ruleInformation.rule &&
220
+ issue.ruleInformation.rule.debtRemFnCoeff !== undefined),
221
+ ...(0, global_1.conditionallyProvideAttribute)('Debt Rem Fn Offset', 'debtRemFnOffset' in issue.ruleInformation.rule &&
222
+ issue.ruleInformation.rule.debtRemFnOffset, 'debtRemFnOffset' in issue.ruleInformation.rule),
223
+ ...(0, global_1.conditionallyProvideAttribute)('Default Debt Rem Fn Coeff', 'defaultDebtRemFnCoeff' in issue.ruleInformation.rule &&
224
+ issue.ruleInformation.rule.defaultDebtRemFnCoeff, 'defaultDebtRemFnCoeff' in issue.ruleInformation.rule &&
225
+ issue.ruleInformation.rule.defaultDebtRemFnCoeff !==
226
+ undefined),
227
+ ...(0, global_1.conditionallyProvideAttribute)('Default Debt Rem Fn Offset', 'defaultDebtRemFnOffset' in issue.ruleInformation.rule &&
228
+ issue.ruleInformation.rule.defaultDebtRemFnOffset, 'defaultDebtRemFnOffset' in issue.ruleInformation.rule),
229
+ ...(0, global_1.conditionallyProvideAttribute)('Education Principles', 'educationPrinciples' in issue.ruleInformation.rule &&
230
+ issue.ruleInformation.rule.educationPrinciples, 'educationPrinciples' in issue.ruleInformation.rule &&
231
+ ((_c = issue.ruleInformation.rule.educationPrinciples) === null || _c === void 0 ? void 0 : _c.length) !== 0),
232
+ ...(0, global_1.conditionallyProvideAttribute)('Effort To Fix Description', 'effortToFixDescription' in issue.ruleInformation.rule &&
233
+ issue.ruleInformation.rule.effortToFixDescription, 'effortToFixDescription' in issue.ruleInformation.rule &&
234
+ issue.ruleInformation.rule.effortToFixDescription !==
235
+ undefined),
236
+ ...(0, global_1.conditionallyProvideAttribute)('Impacts', issue.ruleInformation.rule.impacts, ((_d = issue.ruleInformation.rule.impacts) === null || _d === void 0 ? void 0 : _d.length) !== 0),
237
+ ...(0, global_1.conditionallyProvideAttribute)('Issue Type Vulnerability', true, issue.type === 'VULNERABILITY'),
238
+ ...(0, global_1.conditionallyProvideAttribute)('Issue Type Bug', true, issue.type === 'BUG'),
239
+ ...(0, global_1.conditionallyProvideAttribute)('Issue Type Code Smell', true, issue.type === 'CODE_SMELL'),
240
+ ...(0, global_1.conditionallyProvideAttribute)('Params', issue.ruleInformation.rule.params, ((_e = issue.ruleInformation.rule.params) === null || _e === void 0 ? void 0 : _e.length) !== 0),
241
+ ...(0, global_1.conditionallyProvideAttribute)('Security Standards', issue.ruleInformation.rule.securityStandards, ((_f = issue.ruleInformation.rule.securityStandards) === null || _f === void 0 ? void 0 : _f.length) !== 0),
242
+ ...(0, global_1.conditionallyProvideAttribute)('Sys Tags', issue.ruleInformation.rule.sysTags, ((_g = issue.ruleInformation.rule.sysTags) === null || _g === void 0 ? void 0 : _g.length) !== 0),
243
+ ...(0, global_1.conditionallyProvideAttribute)('Tags', issue.ruleInformation.rule.tags, ((_h = issue.ruleInformation.rule.tags) === null || _h === void 0 ? void 0 : _h.length) !== 0),
244
+ ...(0, global_1.conditionallyProvideAttribute)('Updated At', 'updatedAt' in issue.ruleInformation.rule &&
245
+ issue.ruleInformation.rule.updatedAt, 'updatedAt' in issue.ruleInformation.rule)
246
+ });
247
+ }
248
+ },
249
+ results: [
250
+ {
251
+ status: inspecjs_1.ExecJSON.ControlResultStatus.Failed,
252
+ code_desc: { path: 'codeSnippet' },
253
+ start_time: { path: 'creationDate' },
254
+ message: {
255
+ transformer: (issue) => {
256
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
257
+ return JSON.stringify({
258
+ Message: issue.message,
259
+ Author: issue.author,
260
+ 'Creation Date': issue.creationDate,
261
+ Debt: issue.debt,
262
+ Effort: issue.effort,
263
+ ...(0, global_1.conditionallyProvideAttribute)('Issue Status', issue.issueStatus, ((_a = issue.issueStatus) === null || _a === void 0 ? void 0 : _a.length) !== 0),
264
+ ...(0, global_1.conditionallyProvideAttribute)('Resolution', issue.resolution, ((_b = issue.resolution) === null || _b === void 0 ? void 0 : _b.length) !== 0),
265
+ Status: issue.status,
266
+ 'Update Date': issue.updateDate,
267
+ ...(0, global_1.conditionallyProvideAttribute)('Actions', issue.actions, ((_c = issue.actions) === null || _c === void 0 ? void 0 : _c.length) !== 0),
268
+ ...(0, global_1.conditionallyProvideAttribute)('Attr', issue.attr, issue.attr !== undefined),
269
+ ...(0, global_1.conditionallyProvideAttribute)('Code Variants', 'codeVariants' in issue && issue.codeVariants, 'codeVariants' in issue &&
270
+ ((_d = issue.codeVariants) === null || _d === void 0 ? void 0 : _d.length) !== 0),
271
+ ...(0, global_1.conditionallyProvideAttribute)('Comments', issue.comments, ((_e = issue.comments) === null || _e === void 0 ? void 0 : _e.length) !== 0),
272
+ ...(0, global_1.conditionallyProvideAttribute)('Flows', issue.flows, ((_f = issue.flows) === null || _f === void 0 ? void 0 : _f.length) !== 0),
273
+ ...(0, global_1.conditionallyProvideAttribute)('From Hotspot', 'fromHotspot' in issue && issue.fromHotspot, 'fromHotspot' in issue &&
274
+ issue.fromHotspot !== undefined &&
275
+ issue.fromHotspot !== null),
276
+ Hash: issue.hash,
277
+ Key: issue.key,
278
+ ...(0, global_1.conditionallyProvideAttribute)('Message Formattings', issue.messageFormattings, ((_g = issue.messageFormattings) === null || _g === void 0 ? void 0 : _g.length) !== 0),
279
+ ...(0, global_1.conditionallyProvideAttribute)('Prioritized Rule', 'prioritizedRule' in issue && issue.prioritizedRule, 'prioritizedRule' in issue),
280
+ ...(0, global_1.conditionallyProvideAttribute)('Project Name', issue.projectName, ((_h = issue.projectName) === null || _h === void 0 ? void 0 : _h.length) !== 0),
281
+ ...(0, global_1.conditionallyProvideAttribute)('Quick Fix Available', 'quickFixAvailable' in issue &&
282
+ issue.quickFixAvailable, 'quickFixAvailable' in issue &&
283
+ issue.quickFixAvailable !== undefined),
284
+ ...(0, global_1.conditionallyProvideAttribute)('Rule Description Context Key', 'ruleDescriptionContextKey' in issue &&
285
+ issue.ruleDescriptionContextKey, 'ruleDescriptionContextKey' in issue &&
286
+ ((_j = issue.ruleDescriptionContextKey) === null || _j === void 0 ? void 0 : _j.length) !== 0),
287
+ ...(0, global_1.conditionallyProvideAttribute)('Tags', issue.tags, ((_k = issue.tags) === null || _k === void 0 ? void 0 : _k.length) !== 0),
288
+ ...(0, global_1.conditionallyProvideAttribute)('Transitions', issue.transitions, ((_l = issue.transitions) === null || _l === void 0 ? void 0 : _l.length) !== 0)
289
+ }, null, 2);
290
+ }
291
+ }
292
+ }
293
+ ],
294
+ transformer: () => ({
295
+ descriptions: {
296
+ transformer: (issue) => {
297
+ const rule = issue.ruleInformation.rule;
298
+ if (rule.descriptionSections &&
299
+ rule.descriptionSections.length > 0) {
300
+ const def = rule.descriptionSections.find((d) => d.key === 'default');
301
+ const introduction = rule.descriptionSections.find((d) => d.key === 'introduction');
302
+ const rootcause = rule.descriptionSections.find((d) => d.key === 'root_cause');
303
+ const check = rule.descriptionSections.find((d) => d.key === 'assess_the_problem');
304
+ const fix = rule.descriptionSections.find((d) => d.key === 'how_to_fix');
305
+ const remainder = rule.descriptionSections.filter((d) => ![
306
+ 'default',
307
+ 'introduction',
308
+ 'root_cause',
309
+ 'assess_the_problem',
310
+ 'how_to_fix'
311
+ ].includes(d.key));
312
+ const sections = [
313
+ def,
314
+ def ? introduction : undefined,
315
+ def ? rootcause : undefined,
316
+ check,
317
+ fix,
318
+ ...remainder
319
+ ]
320
+ .filter((s) => s !== undefined)
321
+ .map((s) => ({
322
+ data: s.content,
323
+ label: s.key === 'assess_the_problem'
324
+ ? 'check'
325
+ : s.key === 'how_to_fix'
326
+ ? 'fix'
327
+ : s.key
328
+ }));
329
+ if (sections) {
330
+ return sections;
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+ }
336
+ })
337
+ }
338
+ ],
339
+ sha256: ''
340
+ }
341
+ ],
342
+ passthrough: {
343
+ transformer: (data) => {
344
+ return {
345
+ auxiliary_data: [
346
+ {
347
+ name: 'SonarQube',
348
+ data: {
349
+ ..._.omit(data, 'search.issues')
350
+ }
351
+ }
352
+ ],
353
+ ...(0, global_1.conditionallyProvideAttribute)('raw', data, this.withRaw)
354
+ };
355
+ }
356
+ }
53
357
  };
54
- this.sonarQubeHost = '';
55
- this.projectId = '';
56
- this.userToken = '';
57
- this.branchName = '';
58
- this.pullRequestID = '';
59
- this.sonarQubeHost = sonarQubeHost;
60
- this.projectId = projectId;
358
+ this.withRaw = withRaw;
359
+ }
360
+ }
361
+ exports.SonarqubeMapper = SonarqubeMapper;
362
+ var AuthenticationMethod;
363
+ (function (AuthenticationMethod) {
364
+ AuthenticationMethod[AuthenticationMethod["TokenAsUsername"] = 0] = "TokenAsUsername";
365
+ AuthenticationMethod[AuthenticationMethod["BearerToken"] = 1] = "BearerToken";
366
+ })(AuthenticationMethod || (AuthenticationMethod = {}));
367
+ class SonarqubeResults {
368
+ constructor(sonarqubeHost, projectKey, userToken, branchName, pullRequestID, organization, withRaw = false) {
369
+ this.sonarqubeHost = sonarqubeHost;
370
+ this.projectKey = projectKey;
61
371
  this.userToken = userToken;
62
372
  this.branchName = branchName;
63
373
  this.pullRequestID = pullRequestID;
374
+ this.organization = organization;
375
+ this.withRaw = withRaw;
64
376
  }
65
- async toHdf() {
66
- return this.getProjectData();
377
+ logAxiosError(e) {
378
+ if (e.response) {
379
+ logger.debug('response');
380
+ logger.debug(e.response.status);
381
+ logger.debug(e.response.data);
382
+ }
383
+ if (e.request) {
384
+ logger.debug('request');
385
+ logger.debug(e.request);
386
+ }
387
+ if (e.message) {
388
+ logger.debug('message');
389
+ logger.debug('Error', e.message);
390
+ }
67
391
  }
68
- async getProjectData() {
69
- var _a, _b;
392
+ async getSearchResults() {
70
393
  let paging = true;
71
394
  let page = 1;
395
+ const results = {
396
+ components: [],
397
+ effortTotal: 0,
398
+ facets: [],
399
+ issues: [],
400
+ paging: { pageIndex: 0, pageSize: 0, total: 0 }
401
+ };
72
402
  while (paging) {
73
403
  await axios_1.default
74
- .get(`${this.sonarQubeHost}/api/issues/search`, {
75
- auth: { username: this.userToken, password: '' },
404
+ .get(`${this.sonarqubeHost}/api/issues/search`, {
405
+ ...(this.authMethod === AuthenticationMethod.TokenAsUsername && {
406
+ auth: { username: this.userToken, password: '' }
407
+ }),
408
+ ...(this.authMethod === AuthenticationMethod.BearerToken && {
409
+ headers: { Authorization: `Bearer ${this.userToken}` }
410
+ }),
76
411
  params: {
77
- componentKeys: this.projectId,
78
- types: 'VULNERABILITY',
412
+ componentKeys: this.projectKey,
79
413
  statuses: 'OPEN,REOPENED,CONFIRMED,RESOLVED',
80
414
  p: page,
81
415
  ...(this.branchName && { branch: this.branchName }),
@@ -83,119 +417,145 @@ class SonarQubeResults {
83
417
  }
84
418
  })
85
419
  .then(({ data }) => {
86
- var _a;
87
- if (data.issues) {
88
- this.data.issues.push(...data.issues);
89
- }
90
- paging = ((_a = data.paging) === null || _a === void 0 ? void 0 : _a.total) === 100;
420
+ _.mergeWith(results, data, (objValue, srcValue) => _.isArray(objValue) ? objValue.concat(srcValue) : undefined);
421
+ paging =
422
+ data.paging.pageIndex * data.paging.pageSize <= data.paging.total;
91
423
  page += 1;
424
+ })
425
+ .catch((e) => {
426
+ this.logAxiosError(e);
427
+ return Promise.reject(new Error('Failed at getting Sonarqube issue'));
92
428
  });
93
429
  }
94
- await Promise.all((_a = this.data.issues) === null || _a === void 0 ? void 0 : _a.map((issue) => axios_1.default
95
- .get(`${this.sonarQubeHost}/api/sources/raw`, {
96
- auth: { username: this.userToken, password: '' },
97
- params: {
98
- key: issue.component,
99
- ...(this.branchName && { branch: this.branchName })
100
- }
101
- })
102
- .then((response) => {
103
- var _a, _b;
104
- return (issue.snip = response.data
430
+ return results;
431
+ }
432
+ async getCodeSnippets(issues) {
433
+ const getFullFile = async (component) => {
434
+ return axios_1.default
435
+ .get(`${this.sonarqubeHost}/api/sources/raw`, {
436
+ ...(this.authMethod === AuthenticationMethod.TokenAsUsername && {
437
+ auth: { username: this.userToken, password: '' }
438
+ }),
439
+ ...(this.authMethod === AuthenticationMethod.BearerToken && {
440
+ headers: { Authorization: `Bearer ${this.userToken}` }
441
+ }),
442
+ params: {
443
+ key: component,
444
+ ...(this.branchName && { branch: this.branchName }),
445
+ ...(this.pullRequestID && { pullRequest: this.pullRequestID })
446
+ },
447
+ responseType: 'text'
448
+ })
449
+ .then(({ data }) => data)
450
+ .catch((e) => {
451
+ this.logAxiosError(e);
452
+ return Promise.reject(new Error(`Failed at getting Sonarqube code snippet for ${component}`));
453
+ });
454
+ };
455
+ const applyLineNumber = (snippet) => snippet
456
+ .split('\n')
457
+ .map((l, i) => `${i + 1} ${l}`)
458
+ .join('\n');
459
+ const getContextualizedSnippet = (fullFiles, component, startLine, endLine, msg) => {
460
+ const linenumberedFile = applyLineNumber(fullFiles[component]);
461
+ const snippet = linenumberedFile
105
462
  .split('\n')
106
- .slice(Math.max(((_a = issue.textRange) === null || _a === void 0 ? void 0 : _a.startLine) - 3, 0), ((_b = issue.textRange) === null || _b === void 0 ? void 0 : _b.endLine) + 3)
463
+ .slice(Math.max(startLine - 3, 0), endLine + 3)
464
+ .join('\n')
465
+ .trim();
466
+ const location = `${component}:${startLine}-${endLine}\n`;
467
+ const message = msg ? `${msg}\n` : '';
468
+ return `${location}${message}<pre>\n${snippet}\n</pre>`;
469
+ };
470
+ const components = _.uniq(issues.flatMap((issue) => issue.flows.length
471
+ ? issue.flows.flatMap((flow) => flow.locations.map((location) => location.component))
472
+ : [issue.component]));
473
+ const fullFilePromises = await Promise.all(components.map((component) => getFullFile(component)));
474
+ const fullFiles = Object.fromEntries(_.zip(components, fullFilePromises));
475
+ const snippets = issues.map((issue) => issue.flows.length
476
+ ? issue.flows
477
+ .flatMap((flow) => flow.locations.map((location) => getContextualizedSnippet(fullFiles, location.component, location.textRange.startLine, location.textRange.endLine, location.msg)))
107
478
  .join('\n')
108
- .trim());
109
- })));
110
- await Promise.all((_b = this.data.issues) === null || _b === void 0 ? void 0 : _b.map((issue) => axios_1.default
111
- .get(`${this.sonarQubeHost}/api/rules/show`, {
112
- auth: { username: this.userToken, password: '' },
479
+ : getContextualizedSnippet(fullFiles, issue.component, issue.textRange.startLine, issue.textRange.endLine));
480
+ return snippets;
481
+ }
482
+ async getRules(issues) {
483
+ const getRule = async (rule, organization) => axios_1.default
484
+ .get(`${this.sonarqubeHost}/api/rules/show`, {
485
+ ...(this.authMethod === AuthenticationMethod.TokenAsUsername && {
486
+ auth: { username: this.userToken, password: '' }
487
+ }),
488
+ ...(this.authMethod === AuthenticationMethod.BearerToken && {
489
+ headers: { Authorization: `Bearer ${this.userToken}` }
490
+ }),
113
491
  params: {
114
- key: issue.rule
492
+ key: rule,
493
+ ...((organization || this.organization) && {
494
+ organization: organization || this.organization
495
+ })
115
496
  }
116
497
  })
117
- .then((response) => {
118
- issue.sysTags = response.data.rule.sysTags;
119
- issue.name = response.data.rule.name;
120
- issue.summary = response.data.rule.htmlDesc;
121
- })));
122
- const result = new SonarQubeMapper(this.data, this.projectId, this.branchName, this.pullRequestID);
123
- return result.toHdf();
498
+ .then(({ data }) => data)
499
+ .catch((e) => {
500
+ this.logAxiosError(e);
501
+ return Promise.reject(new Error('Failed at getting Sonarqube rule'));
502
+ });
503
+ const rulesAndOrgs = _.uniqWith(issues.map((issue) => [issue.rule, issue.organization]), _.isEqual);
504
+ const fullRulePromises = await Promise.all(rulesAndOrgs.map((ruleAndOrg) => getRule(...ruleAndOrg)));
505
+ const fullRules = Object.fromEntries(_.zip(rulesAndOrgs.map((ruleAndOrg) => ruleAndOrg.join('\n')), fullRulePromises));
506
+ const rules = issues.map((issue) => fullRules[[issue.rule, issue.organization].join('\n')]);
507
+ return rules;
124
508
  }
125
- }
126
- exports.SonarQubeResults = SonarQubeResults;
127
- function createSonarqubeMappings(projectName, branchName, pullRequestID) {
128
- const scanDescriptionModifier = (branchName ? ` Branch ${branchName}` : '') +
129
- (pullRequestID ? ` Pull Request ${pullRequestID}` : '');
130
- return {
131
- platform: {
132
- name: 'Heimdall Tools',
133
- release: package_json_1.version,
134
- target_id: projectName
135
- },
136
- version: package_json_1.version,
137
- statistics: {
138
- duration: null
139
- },
140
- profiles: [
141
- {
142
- name: 'Sonarqube Scan',
143
- version: null,
144
- title: `SonarQube Scan of Project ${projectName}${scanDescriptionModifier}`,
145
- maintainer: null,
146
- summary: `SonarQube Scan of Project ${projectName}${scanDescriptionModifier}`,
147
- license: null,
148
- copyright: null,
149
- copyright_email: null,
150
- supports: [],
151
- attributes: [],
152
- depends: [],
153
- groups: [],
154
- status: 'loaded',
155
- controls: [
156
- {
157
- path: 'issues',
158
- key: 'id',
159
- desc: { path: 'summary' },
160
- descriptions: [],
161
- refs: [],
162
- source_location: {},
163
- id: { path: 'rule' },
164
- title: { path: 'name' },
165
- impact: {
166
- path: 'severity',
167
- transformer: (0, base_converter_1.impactMapping)(IMPACT_MAPPING)
168
- },
169
- code: null,
170
- tags: {
171
- cci: {
172
- transformer: (issue) => (0, global_1.getCCIsForNISTTags)(parseNistTags(issue))
173
- },
174
- nist: { transformer: parseNistTags }
175
- },
176
- results: [
177
- {
178
- status: inspecjs_1.ExecJSON.ControlResultStatus.Failed,
179
- code_desc: { transformer: formatCodeDesc },
180
- run_time: 0,
181
- start_time: ''
182
- }
183
- ]
184
- }
185
- ],
186
- sha256: ''
509
+ async generateHdf(sonarqubeVersion) {
510
+ const searchResults = await this.getSearchResults();
511
+ logger.debug(`Got ${searchResults.issues.length} issues`);
512
+ const codeSnippets = await this.getCodeSnippets(searchResults.issues);
513
+ logger.debug(`Got ${codeSnippets.length} code snippets`);
514
+ const rules = await this.getRules(searchResults.issues);
515
+ logger.debug(`Got ${rules.length} rules`);
516
+ const data = {
517
+ sonarqubeVersion,
518
+ sonarqubeHost: this.sonarqubeHost,
519
+ projectKey: this.projectKey,
520
+ branchName: this.branchName,
521
+ pullRequestID: this.pullRequestID,
522
+ organization: this.organization,
523
+ search: {
524
+ ...searchResults,
525
+ issues: searchResults.issues.map((issue, index) => ({
526
+ ...issue,
527
+ codeSnippet: codeSnippets[index],
528
+ ruleInformation: rules[index]
529
+ }))
187
530
  }
188
- ]
189
- };
190
- }
191
- class SonarQubeMapper extends base_converter_1.BaseConverter {
192
- constructor(issuesJSON, projectName, branchName, pullRequestID) {
193
- super(issuesJSON);
194
- this.projectName = '';
195
- this.branchName = '';
196
- this.pullRequestID = '';
197
- super.setMappings(createSonarqubeMappings(projectName, branchName, pullRequestID));
531
+ };
532
+ return new SonarqubeMapper(data, this.withRaw).toHdf();
533
+ }
534
+ async toHdf() {
535
+ const sonarqubeVersion = await axios_1.default
536
+ .get(`${this.sonarqubeHost}/api/server/version`)
537
+ .then(({ data }) => data);
538
+ logger.debug(`Generating HDF for ${this.sonarqubeHost} version: ${sonarqubeVersion}`);
539
+ this.authMethod = isSonarqubeVersionNine(sonarqubeVersion)
540
+ ? AuthenticationMethod.TokenAsUsername
541
+ : AuthenticationMethod.BearerToken;
542
+ if (isSonarqubeVersionEight(sonarqubeVersion)) {
543
+ return this.generateHdf(sonarqubeVersion);
544
+ }
545
+ else if (isSonarqubeVersionNine(sonarqubeVersion)) {
546
+ return this.generateHdf(sonarqubeVersion);
547
+ }
548
+ else if (isSonarqubeVersionTen(sonarqubeVersion)) {
549
+ return this.generateHdf(sonarqubeVersion);
550
+ }
551
+ else if (isSonarqubeVersionTwenty_five(sonarqubeVersion)) {
552
+ return this.generateHdf(sonarqubeVersion);
553
+ }
554
+ else {
555
+ logger.debug(`Sonarqube version ${sonarqubeVersion} is not formally supported. Please create an issue at https://github.com/mitre/heimdall2/issues if something is broken.`);
556
+ return this.generateHdf(sonarqubeVersion);
557
+ }
198
558
  }
199
559
  }
200
- exports.SonarQubeMapper = SonarQubeMapper;
560
+ exports.SonarqubeResults = SonarqubeResults;
201
561
  //# sourceMappingURL=sonarqube-mapper.js.map