@contrast/contrast 1.0.10 → 1.0.13

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.
Files changed (112) hide show
  1. package/README.md +1 -1
  2. package/dist/audit/{languageAnalysisEngine/report → report}/commonReportingFunctions.js +56 -35
  3. package/dist/audit/report/models/reportGuidanceModel.js +6 -0
  4. package/dist/audit/{languageAnalysisEngine/report → report}/models/reportLibraryModel.js +0 -0
  5. package/dist/audit/{languageAnalysisEngine/report → report}/models/reportListModel.js +0 -0
  6. package/dist/audit/{languageAnalysisEngine/report → report}/models/reportOutputModel.js +1 -2
  7. package/dist/audit/{languageAnalysisEngine/report → report}/models/reportSeverityModel.js +0 -0
  8. package/dist/audit/{languageAnalysisEngine/report → report}/models/severityCountModel.js +1 -0
  9. package/dist/audit/{languageAnalysisEngine/report → report}/reportingFeature.js +12 -8
  10. package/dist/audit/{languageAnalysisEngine/report → report}/utils/reportUtils.js +3 -4
  11. package/dist/commands/audit/auditConfig.js +3 -3
  12. package/dist/commands/audit/help.js +3 -1
  13. package/dist/commands/audit/processAudit.js +14 -2
  14. package/dist/commands/auth/auth.js +1 -1
  15. package/dist/commands/config/config.js +2 -2
  16. package/dist/commands/scan/processScan.js +20 -4
  17. package/dist/commands/scan/sca/scaAnalysis.js +15 -5
  18. package/dist/common/HTTPClient.js +39 -2
  19. package/dist/common/commonHelp.js +19 -0
  20. package/dist/common/fail.js +70 -0
  21. package/dist/common/versionChecker.js +14 -6
  22. package/dist/constants/constants.js +2 -2
  23. package/dist/constants/locales.js +15 -5
  24. package/dist/constants.js +42 -5
  25. package/dist/index.js +6 -3
  26. package/dist/lambda/help.js +2 -3
  27. package/dist/lambda/lambda.js +7 -0
  28. package/dist/scaAnalysis/common/scaParserForGoAndJava.js +32 -0
  29. package/dist/scaAnalysis/common/scaServicesUpload.js +52 -0
  30. package/dist/scaAnalysis/common/treeUpload.js +20 -5
  31. package/dist/scaAnalysis/dotnet/analysis.js +15 -3
  32. package/dist/scaAnalysis/go/goAnalysis.js +8 -2
  33. package/dist/scaAnalysis/java/analysis.js +10 -6
  34. package/dist/scaAnalysis/java/index.js +7 -1
  35. package/dist/scaAnalysis/java/javaBuildDepsParser.js +19 -3
  36. package/dist/scaAnalysis/javascript/index.js +4 -0
  37. package/dist/scaAnalysis/javascript/scaServiceParser.js +109 -0
  38. package/dist/scaAnalysis/php/analysis.js +1 -1
  39. package/dist/scaAnalysis/php/index.js +12 -6
  40. package/dist/scaAnalysis/php/phpNewServicesMapper.js +62 -0
  41. package/dist/scaAnalysis/python/analysis.js +43 -5
  42. package/dist/scaAnalysis/python/index.js +7 -2
  43. package/dist/scaAnalysis/ruby/analysis.js +116 -9
  44. package/dist/scaAnalysis/ruby/index.js +6 -1
  45. package/dist/scan/formatScanOutput.js +6 -5
  46. package/dist/scan/help.js +2 -3
  47. package/dist/scan/populateProjectIdAndProjectName.js +5 -0
  48. package/dist/scan/scan.js +4 -0
  49. package/dist/scan/scanConfig.js +4 -4
  50. package/dist/scan/scanResults.js +46 -3
  51. package/dist/telemetry/telemetry.js +137 -0
  52. package/dist/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
  53. package/dist/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +14 -5
  54. package/dist/utils/getConfig.js +2 -4
  55. package/dist/utils/parsedCLIOptions.js +3 -1
  56. package/dist/utils/requestUtils.js +7 -1
  57. package/package.json +4 -2
  58. package/src/audit/{languageAnalysisEngine/report → report}/commonReportingFunctions.ts +80 -44
  59. package/src/audit/report/models/reportGuidanceModel.ts +5 -0
  60. package/src/audit/{languageAnalysisEngine/report → report}/models/reportLibraryModel.ts +0 -0
  61. package/src/audit/{languageAnalysisEngine/report → report}/models/reportListModel.ts +0 -0
  62. package/src/audit/{languageAnalysisEngine/report → report}/models/reportOutputModel.ts +1 -7
  63. package/src/audit/{languageAnalysisEngine/report → report}/models/reportSeverityModel.ts +0 -0
  64. package/src/audit/{languageAnalysisEngine/report → report}/models/severityCountModel.ts +2 -0
  65. package/src/audit/{languageAnalysisEngine/report → report}/reportingFeature.ts +16 -9
  66. package/src/audit/{languageAnalysisEngine/report → report}/utils/reportUtils.ts +4 -4
  67. package/src/commands/audit/auditConfig.ts +10 -3
  68. package/src/commands/audit/help.ts +3 -1
  69. package/src/commands/audit/processAudit.ts +24 -2
  70. package/src/commands/auth/auth.js +3 -1
  71. package/src/commands/config/config.js +4 -2
  72. package/src/commands/scan/processScan.js +32 -4
  73. package/src/commands/scan/sca/scaAnalysis.js +23 -5
  74. package/src/common/HTTPClient.js +59 -2
  75. package/src/common/commonHelp.ts +13 -0
  76. package/src/common/fail.js +79 -0
  77. package/src/common/versionChecker.ts +18 -8
  78. package/src/constants/constants.js +2 -2
  79. package/src/constants/locales.js +19 -7
  80. package/src/constants.js +46 -6
  81. package/src/index.ts +18 -4
  82. package/src/lambda/help.ts +2 -3
  83. package/src/lambda/lambda.ts +12 -0
  84. package/src/scaAnalysis/common/scaParserForGoAndJava.js +41 -0
  85. package/src/scaAnalysis/common/scaServicesUpload.js +54 -0
  86. package/src/scaAnalysis/common/treeUpload.js +21 -5
  87. package/src/scaAnalysis/dotnet/analysis.js +21 -3
  88. package/src/scaAnalysis/go/goAnalysis.js +9 -2
  89. package/src/scaAnalysis/java/analysis.js +11 -6
  90. package/src/scaAnalysis/java/index.js +9 -1
  91. package/src/scaAnalysis/java/javaBuildDepsParser.js +25 -6
  92. package/src/scaAnalysis/javascript/index.js +4 -0
  93. package/src/scaAnalysis/javascript/scaServiceParser.js +145 -0
  94. package/src/scaAnalysis/php/analysis.js +1 -1
  95. package/src/scaAnalysis/php/index.js +12 -6
  96. package/src/scaAnalysis/php/phpNewServicesMapper.js +77 -0
  97. package/src/scaAnalysis/python/analysis.js +49 -5
  98. package/src/scaAnalysis/python/index.js +7 -2
  99. package/src/scaAnalysis/ruby/analysis.js +149 -9
  100. package/src/scaAnalysis/ruby/index.js +6 -1
  101. package/src/scan/formatScanOutput.ts +7 -5
  102. package/src/scan/help.js +2 -3
  103. package/src/scan/populateProjectIdAndProjectName.js +5 -1
  104. package/src/scan/scan.ts +4 -0
  105. package/src/scan/scanConfig.js +6 -4
  106. package/src/scan/scanResults.js +52 -3
  107. package/src/telemetry/telemetry.ts +154 -0
  108. package/src/{audit/languageAnalysisEngine/util → utils}/capabilities.js +0 -0
  109. package/src/{audit/languageAnalysisEngine/util → utils}/generalAPI.js +16 -6
  110. package/src/utils/getConfig.ts +2 -11
  111. package/src/utils/parsedCLIOptions.js +14 -1
  112. package/src/utils/requestUtils.js +8 -1
@@ -0,0 +1,77 @@
1
+ const { keyBy, merge } = require('lodash')
2
+
3
+ const parsePHPLockFileForScaServices = phpLockFile => {
4
+ const packages = keyBy(phpLockFile.packages, 'name')
5
+ const packagesDev = keyBy(phpLockFile['packages-dev'], 'name')
6
+
7
+ return merge(buildDepTree(packages, true), buildDepTree(packagesDev, false))
8
+ }
9
+
10
+ const buildDepTree = (packages, isProduction) => {
11
+ //builds deps into flat structure
12
+ const dependencyTree = {}
13
+
14
+ for (const packagesKey in packages) {
15
+ const currentObj = packages[packagesKey]
16
+ const { group, name } = findGroupAndName(currentObj.name)
17
+
18
+ const key = `${group}/${name}@${currentObj.version}`
19
+ dependencyTree[key] = {
20
+ group: group,
21
+ name: name,
22
+ version: currentObj.version,
23
+ directDependency: true,
24
+ isProduction: isProduction,
25
+ dependencies: []
26
+ }
27
+
28
+ const mergedChildDeps = merge(
29
+ buildSubDepsIntoFlatStructure(currentObj.require),
30
+ buildSubDepsIntoFlatStructure(currentObj['require-dev'])
31
+ )
32
+
33
+ for (const childKey in mergedChildDeps) {
34
+ const { group, name } = findGroupAndName(childKey)
35
+ const builtKey = `${group}/${name}`
36
+ dependencyTree[builtKey] = mergedChildDeps[childKey]
37
+ }
38
+ }
39
+ return dependencyTree
40
+ }
41
+
42
+ // currently sub deps will be built into a flat structure
43
+ // but not ingested via the new services as they do not have concrete versions
44
+ const buildSubDepsIntoFlatStructure = childDeps => {
45
+ const dependencyTree = {}
46
+
47
+ for (const dep in childDeps) {
48
+ const version = childDeps[dep]
49
+ const { group, name } = findGroupAndName(dep)
50
+ const key = `${group}/${name}`
51
+ dependencyTree[key] = {
52
+ group: group,
53
+ name: name,
54
+ version: version,
55
+ directDependency: false,
56
+ isProduction: false,
57
+ dependencies: []
58
+ }
59
+ }
60
+ return dependencyTree
61
+ }
62
+
63
+ const findGroupAndName = groupAndName => {
64
+ if (groupAndName.includes('/')) {
65
+ const groupName = groupAndName.split('/')
66
+ return { group: groupName[0], name: groupName[1] }
67
+ } else {
68
+ return { group: groupAndName, name: groupAndName }
69
+ }
70
+ }
71
+
72
+ module.exports = {
73
+ parsePHPLockFileForScaServices,
74
+ buildDepTree,
75
+ buildSubDepsIntoFlatStructure,
76
+ findGroupAndName
77
+ }
@@ -1,5 +1,6 @@
1
1
  const multiReplace = require('string-multiple-replace')
2
2
  const fs = require('fs')
3
+ const i18n = require('i18n')
3
4
 
4
5
  const readAndParseProjectFile = file => {
5
6
  const filePath = filePathForWindows(file + '/Pipfile')
@@ -23,12 +24,52 @@ const readAndParseLockFile = file => {
23
24
  return parsedPipLock
24
25
  }
25
26
 
26
- const getPythonDeps = config => {
27
+ const readLockFile = file => {
28
+ const filePath = filePathForWindows(file + '/Pipfile.lock')
29
+ const lockFile = fs.readFileSync(filePath, 'utf8')
30
+ let parsedPipLock = JSON.parse(lockFile)
31
+ return parsedPipLock['default']
32
+ }
33
+
34
+ const scaPythonParser = pythonDependencies => {
35
+ let pythonParsedDeps = {}
36
+ for (let key in pythonDependencies) {
37
+ pythonParsedDeps[key] = {}
38
+ pythonParsedDeps[key].version = pythonDependencies[key].version.replace(
39
+ '==',
40
+ ''
41
+ )
42
+ pythonParsedDeps[key].group = null
43
+ pythonParsedDeps[key].name = key
44
+ pythonParsedDeps[key].isProduction = true
45
+ pythonParsedDeps[key].dependencies = []
46
+ pythonParsedDeps[key].directDependency = true
47
+ }
48
+ return pythonParsedDeps
49
+ }
50
+
51
+ const checkForCorrectFiles = languageFiles => {
52
+ if (!languageFiles.includes('Pipfile.lock')) {
53
+ throw new Error(i18n.__('languageAnalysisHasNoLockFile', 'python'))
54
+ }
55
+
56
+ if (!languageFiles.includes('Pipfile')) {
57
+ throw new Error(i18n.__('languageAnalysisProjectFileError', 'python'))
58
+ }
59
+ }
60
+
61
+ const getPythonDeps = (config, languageFiles) => {
27
62
  try {
28
- const parseProject = readAndParseProjectFile(config.file)
29
- const parsePip = readAndParseLockFile(config.file)
63
+ if (config.experimental) {
64
+ let pythonLockFileContents = readLockFile(config.file)
65
+ return scaPythonParser(pythonLockFileContents)
66
+ } else {
67
+ checkForCorrectFiles(languageFiles)
68
+ const parseProject = readAndParseProjectFile(config.file)
69
+ const parsePip = readAndParseLockFile(config.file)
30
70
 
31
- return { pipfileLock: parsePip, pipfilDependanceies: parseProject }
71
+ return { pipfileLock: parsePip, pipfilDependanceies: parseProject }
72
+ }
32
73
  } catch (err) {
33
74
  console.log(err.message.toString())
34
75
  process.exit(1)
@@ -44,6 +85,9 @@ const filePathForWindows = path => {
44
85
 
45
86
  module.exports = {
46
87
  getPythonDeps,
88
+ scaPythonParser,
89
+ readAndParseLockFile,
47
90
  readAndParseProjectFile,
48
- readAndParseLockFile
91
+ checkForCorrectFiles,
92
+ readLockFile
49
93
  }
@@ -1,9 +1,14 @@
1
1
  const { createPythonTSMessage } = require('../common/formatMessage')
2
- const { getPythonDeps } = require('./analysis')
2
+ const { getPythonDeps, secondaryParser } = require('./analysis')
3
3
 
4
4
  const pythonAnalysis = (config, languageFiles) => {
5
5
  const pythonDeps = getPythonDeps(config, languageFiles.PYTHON)
6
- return createPythonTSMessage(pythonDeps)
6
+
7
+ if (config.experimental) {
8
+ return pythonDeps
9
+ } else {
10
+ return createPythonTSMessage(pythonDeps)
11
+ }
7
12
  }
8
13
 
9
14
  module.exports = {
@@ -1,4 +1,26 @@
1
1
  const fs = require('fs')
2
+ const i18n = require('i18n')
3
+
4
+ const getRubyDeps = (config, languageFiles) => {
5
+ try {
6
+ checkForCorrectFiles(languageFiles)
7
+ const parsedGem = readAndParseGemfile(config.file)
8
+ const parsedLock = readAndParseGemLockFile(config.file)
9
+ if (config.experimental) {
10
+ const rubyArray = removeRedundantAndPopulateDefinedElements(
11
+ parsedLock.sources
12
+ )
13
+ let rubyTree = createRubyTree(rubyArray)
14
+ findChildrenDependencies(rubyTree)
15
+ processRootDependencies(parsedLock.dependencies, rubyTree)
16
+ return rubyTree
17
+ } else {
18
+ return { gemfilesDependanceies: parsedGem, gemfileLock: parsedLock }
19
+ }
20
+ } catch (err) {
21
+ throw err
22
+ }
23
+ }
2
24
 
3
25
  const readAndParseGemfile = file => {
4
26
  const gemFile = fs.readFileSync(file + '/Gemfile', 'utf8')
@@ -241,15 +263,128 @@ const buildSourceDependencyWithVersion = (
241
263
  return dependencies
242
264
  }
243
265
 
244
- const getRubyDeps = config => {
245
- try {
246
- const parsedGem = readAndParseGemfile(config.file)
247
- const parsedLock = readAndParseGemLockFile(config.file)
266
+ const processRootDependencies = (rootDependencies, rubyTree) => {
267
+ const getParentObjectByName = queryToken =>
268
+ Object.values(rubyTree).filter(({ name }) => name === queryToken)
248
269
 
249
- return { gemfilesDependanceies: parsedGem, gemfileLock: parsedLock }
250
- } catch (err) {
251
- console.log(err.message)
252
- process.exit(1)
270
+ for (let parent in rootDependencies) {
271
+ let parentObject = getParentObjectByName(parent)
272
+
273
+ // ignore root dependencies that don't have a resolved version
274
+ if (parentObject[0]) {
275
+ let gav =
276
+ parentObject[0].group +
277
+ '/' +
278
+ parentObject[0].name +
279
+ '@' +
280
+ parentObject[0].version
281
+
282
+ rubyTree[gav] = parentObject[0]
283
+ rubyTree[gav].directDependency = true
284
+ }
285
+ }
286
+ return rubyTree
287
+ }
288
+
289
+ const createRubyTree = rubyArray => {
290
+ let rubyTree = {}
291
+ for (let x in rubyArray) {
292
+ let version = rubyArray[x].resolved
293
+
294
+ let gav = rubyArray[x].group + '/' + rubyArray[x].name + '@' + version
295
+ rubyTree[gav] = rubyArray[x]
296
+ rubyTree[gav].directDependency = false
297
+ rubyTree[gav].version = version
298
+
299
+ // add dependency array if none exists
300
+ if (!rubyTree[gav].dependencies) {
301
+ rubyTree[gav].dependencies = []
302
+ }
303
+
304
+ delete rubyTree[gav].resolved
305
+ }
306
+ return rubyTree
307
+ }
308
+
309
+ const findChildrenDependencies = rubyTree => {
310
+ for (let dep in rubyTree) {
311
+ let unResolvedChildDepsKey = Object.keys(rubyTree[dep].dependencies)
312
+ rubyTree[dep].dependencies = resolveVersionOfChildDependencies(
313
+ unResolvedChildDepsKey,
314
+ rubyTree
315
+ )
316
+ }
317
+ }
318
+
319
+ const resolveVersionOfChildDependencies = (
320
+ unResolvedChildDepsKey,
321
+ rubyObject
322
+ ) => {
323
+ const getParentObjectByName = queryToken =>
324
+ Object.values(rubyObject).filter(({ name }) => name === queryToken)
325
+ let resolvedChildrenDependencies = []
326
+ for (let childDep in unResolvedChildDepsKey) {
327
+ let childDependencyName = unResolvedChildDepsKey[childDep]
328
+ let parent = getParentObjectByName(childDependencyName)
329
+ resolvedChildrenDependencies.push(
330
+ 'null/' + childDependencyName + '@' + parent[0].version
331
+ )
332
+ }
333
+ return resolvedChildrenDependencies
334
+ }
335
+
336
+ const removeRedundantAndPopulateDefinedElements = deps => {
337
+ return deps.map(element => {
338
+ if (element.sourceType === 'GIT') {
339
+ delete element.sourceType
340
+ delete element.remote
341
+ delete element.platform
342
+
343
+ element.group = null
344
+ element.isProduction = true
345
+ }
346
+
347
+ if (element.sourceType === 'GEM') {
348
+ element.group = null
349
+ element.isProduction = true
350
+
351
+ delete element.sourceType
352
+ delete element.remote
353
+ delete element.platform
354
+ }
355
+
356
+ if (element.sourceType === 'PATH') {
357
+ element.group = null
358
+ element.isProduction = true
359
+
360
+ delete element.platform
361
+ delete element.sourceType
362
+ delete element.remote
363
+ }
364
+
365
+ if (element.sourceType === 'BUNDLED WITH') {
366
+ element.group = null
367
+ element.isProduction = true
368
+
369
+ delete element.sourceType
370
+ delete element.remote
371
+ delete element.branch
372
+ delete element.revision
373
+ delete element.depthLevel
374
+ delete element.specs
375
+ delete element.platform
376
+ }
377
+ return element
378
+ })
379
+ }
380
+
381
+ const checkForCorrectFiles = languageFiles => {
382
+ if (!languageFiles.includes('Gemfile.lock')) {
383
+ throw new Error(i18n.__('languageAnalysisHasNoLockFile', 'ruby'))
384
+ }
385
+
386
+ if (!languageFiles.includes('Gemfile')) {
387
+ throw new Error(i18n.__('languageAnalysisProjectFileError', 'ruby'))
253
388
  }
254
389
  }
255
390
 
@@ -269,5 +404,10 @@ module.exports = {
269
404
  getVersion,
270
405
  getPatchLevel,
271
406
  formatSourceArr,
272
- getSourceArray
407
+ getSourceArray,
408
+ checkForCorrectFiles,
409
+ removeRedundantAndPopulateDefinedElements,
410
+ createRubyTree,
411
+ findChildrenDependencies,
412
+ processRootDependencies
273
413
  }
@@ -3,7 +3,12 @@ const { createRubyTSMessage } = require('../common/formatMessage')
3
3
 
4
4
  const rubyAnalysis = (config, languageFiles) => {
5
5
  const rubyDeps = analysis.getRubyDeps(config, languageFiles.RUBY)
6
- return createRubyTSMessage(rubyDeps)
6
+
7
+ if (config.experimental) {
8
+ return rubyDeps
9
+ } else {
10
+ return createRubyTSMessage(rubyDeps)
11
+ }
7
12
  }
8
13
 
9
14
  module.exports = {
@@ -62,12 +62,12 @@ export function formatScanOutput(scanResults: ScanResultsModel) {
62
62
  })
63
63
  let learnRow: string[] = []
64
64
  let adviceRow = []
65
+ const headerColour = chalk.hex(entry.colour)
65
66
  const headerRow = [
66
- chalk
67
- .hex(entry.colour)
68
- .bold(`CONTRAST-${count.toString().padStart(3, '0')}`),
69
- chalk.hex(entry.colour).bold('-'),
70
- chalk.hex(entry.colour).bold(`[${entry.severity}] ${entry.ruleId}`) +
67
+ headerColour(`CONTRAST-${count.toString().padStart(3, '0')}`),
68
+ headerColour(`-`),
69
+ headerColour(`[${entry.severity}] `) +
70
+ headerColour.bold(`${entry.ruleId}`) +
71
71
  entry.message
72
72
  ]
73
73
 
@@ -104,6 +104,8 @@ export function formatScanOutput(scanResults: ScanResultsModel) {
104
104
  })
105
105
  }
106
106
  printVulnInfo(projectOverview)
107
+
108
+ return projectOverview
107
109
  }
108
110
 
109
111
  function printVulnInfo(projectOverview: any) {
package/src/scan/help.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const commandLineUsage = require('command-line-usage')
2
2
  const i18n = require('i18n')
3
3
  const constants = require('../constants')
4
+ const { commonHelpLinks } = require('../common/commonHelp')
4
5
 
5
6
  const scanUsageGuide = commandLineUsage([
6
7
  {
@@ -35,9 +36,7 @@ const scanUsageGuide = commandLineUsage([
35
36
  'application-name'
36
37
  ]
37
38
  },
38
- {
39
- content: '{underline https://www.contrastsecurity.com}'
40
- }
39
+ commonHelpLinks()
41
40
  ])
42
41
 
43
42
  module.exports = {
@@ -28,7 +28,11 @@ const createProjectId = async (config, client) => {
28
28
  process.exit(1)
29
29
  return
30
30
  }
31
-
31
+ if (res.statusCode === 429) {
32
+ console.log(i18n.__('exceededFreeTier'))
33
+ process.exit(1)
34
+ return
35
+ }
32
36
  if (res.statusCode === 201) {
33
37
  console.log(i18n.__('projectCreatedScan'))
34
38
  if (config.verbose) {
package/src/scan/scan.ts CHANGED
@@ -47,6 +47,10 @@ export const sendScan = async (config: any) => {
47
47
  )
48
48
  console.log(i18n.__('genericServiceError', res.statusCode))
49
49
  }
50
+ if (res.statusCode === 429) {
51
+ console.log(i18n.__('exceededFreeTier'))
52
+ process.exit(1)
53
+ }
50
54
  if (res.statusCode === 403) {
51
55
  console.log(i18n.__('permissionsError'))
52
56
  process.exit(1)
@@ -1,13 +1,15 @@
1
1
  const paramHandler = require('../utils/paramsUtil/paramHandler')
2
- const constants = require('../../src/constants.js')
3
- const parsedCLIOptions = require('../../src/utils/parsedCLIOptions')
2
+ const constants = require('../constants.js')
4
3
  const path = require('path')
5
4
  const { supportedLanguagesScan } = require('../constants/constants')
6
5
  const i18n = require('i18n')
7
6
  const { scanUsageGuide } = require('./help')
7
+ const parsedCLIOptions = require('../utils/parsedCLIOptions')
8
8
 
9
- const getScanConfig = argv => {
10
- let scanParams = parsedCLIOptions.getCommandLineArgsCustom(
9
+ const getScanConfig = async (contrastConf, command, argv) => {
10
+ let scanParams = await parsedCLIOptions.getCommandLineArgsCustom(
11
+ contrastConf,
12
+ command,
11
13
  argv,
12
14
  constants.commandLineDefinitions.scanOptionDefinitions
13
15
  )
@@ -4,11 +4,15 @@ const oraFunctions = require('../utils/oraWrapper')
4
4
  const _ = require('lodash')
5
5
  const i18n = require('i18n')
6
6
  const oraWrapper = require('../utils/oraWrapper')
7
+ const readLine = require('readline')
7
8
 
8
9
  const getScanId = async (config, codeArtifactId, client) => {
9
10
  return client
10
11
  .getScanId(config, codeArtifactId)
11
12
  .then(res => {
13
+ if (res.statusCode == 429) {
14
+ throw new Error(i18n.__('exceededFreeTier'))
15
+ }
12
16
  return res.body.id
13
17
  })
14
18
  .catch(err => {
@@ -88,13 +92,57 @@ const returnScanResults = async (
88
92
  startScanSpinner,
89
93
  'Contrast Scan timed out at the specified ' + timeout + ' seconds.'
90
94
  )
91
- console.log('Please try again, allowing more time.')
92
- process.exit(1)
95
+
96
+ const isCI = process.env.CONTRAST_CODESEC_CI
97
+ ? JSON.parse(process.env.CONTRAST_CODESEC_CI.toLowerCase())
98
+ : false
99
+ if (!isCI) {
100
+ const retry = await retryScanPrompt()
101
+ timeout = retry.timeout
102
+ } else {
103
+ console.log('Please try again, allowing more time')
104
+ process.exit(1)
105
+ }
93
106
  }
94
107
  }
95
108
  }
96
109
  }
97
110
 
111
+ const retryScanPrompt = async () => {
112
+ const rl = readLine.createInterface({
113
+ input: process.stdin,
114
+ output: process.stdout
115
+ })
116
+
117
+ return new Promise((resolve, reject) => {
118
+ requestUtils.timeOutError(30000, reject)
119
+
120
+ rl.question(
121
+ '🔁 Do you want to continue waiting on Scan? [Y/N]\n',
122
+ async input => {
123
+ if (input.toLowerCase() === 'yes' || input.toLowerCase() === 'y') {
124
+ console.log('Continuing wait for Scan')
125
+ rl.close()
126
+ resolve({ timeout: 300 })
127
+ } else if (
128
+ input.toLowerCase() === 'no' ||
129
+ input.toLowerCase() === 'n'
130
+ ) {
131
+ rl.close()
132
+ console.log('Contrast Scan Retry Cancelled: Exiting')
133
+ resolve(process.exit(1))
134
+ } else {
135
+ rl.close()
136
+ console.log('Invalid Input: Exiting')
137
+ resolve(process.exit(1))
138
+ }
139
+ }
140
+ )
141
+ }).catch(e => {
142
+ throw e
143
+ })
144
+ }
145
+
98
146
  const returnScanResultsInstances = async (config, scanId) => {
99
147
  const client = commonApi.getHttpClient(config)
100
148
  let result
@@ -118,5 +166,6 @@ module.exports = {
118
166
  getScanId: getScanId,
119
167
  returnScanResults: returnScanResults,
120
168
  pollScanResults: pollScanResults,
121
- returnScanResultsInstances: returnScanResultsInstances
169
+ returnScanResultsInstances: returnScanResultsInstances,
170
+ retryScanPrompt
122
171
  }
@@ -0,0 +1,154 @@
1
+ import { getHttpClient } from '../utils/commonApi'
2
+ import * as crypto from 'crypto'
3
+ import { ContrastConf } from '../utils/getConfig'
4
+
5
+ export const TELEMETRY_CLI_COMMANDS_EVENT = 'CLI_COMMANDS'
6
+ export const TELEMETRY_CLI_TIME_TO_AUTH_EVENT = 'CLI_TIME_TO_AUTH'
7
+
8
+ export const sendTelemetryConfigAsConfObj = async (
9
+ config: ContrastConf,
10
+ command: string,
11
+ argv: string[],
12
+ result: string,
13
+ language: string
14
+ ) => {
15
+ const hostParam = '--host'
16
+ const hostParamAlias = '-h'
17
+ const orgIdParam = '--organization-id'
18
+ const orgIdParamAlias = '-o'
19
+ const authParam = '--authorization'
20
+ const apiKeyParam = '--api-key'
21
+
22
+ let configToUse
23
+
24
+ if (
25
+ paramExists(argv, hostParam, hostParamAlias) &&
26
+ paramExists(argv, orgIdParam, orgIdParamAlias) &&
27
+ paramExists(argv, authParam, null) &&
28
+ paramExists(argv, apiKeyParam, null)
29
+ ) {
30
+ //if the user has passed the values as params
31
+ configToUse = {
32
+ host: findParamValueFromArgs(argv, hostParam, hostParamAlias),
33
+ organizationId: findParamValueFromArgs(argv, orgIdParam, orgIdParamAlias),
34
+ authorization: findParamValueFromArgs(argv, authParam, null),
35
+ apiKey: findParamValueFromArgs(argv, apiKeyParam, null)
36
+ }
37
+ } else if (
38
+ config &&
39
+ config.get('host') &&
40
+ config.get('organizationId') &&
41
+ config.get('authorization') &&
42
+ config.get('apiKey')
43
+ ) {
44
+ configToUse = {
45
+ host: config.get('host')?.slice(0, -1), //slice off extra / in url, will 404 on teamserver if we don't
46
+ organizationId: config.get('organizationId'),
47
+ authorization: config.get('authorization'),
48
+ apiKey: config.get('apiKey')
49
+ }
50
+ } else {
51
+ //return when unable to get config
52
+ return
53
+ }
54
+
55
+ return await sendTelemetryConfigAsObject(
56
+ configToUse,
57
+ command,
58
+ argv,
59
+ result,
60
+ language
61
+ )
62
+ }
63
+
64
+ export const sendTelemetryConfigAsObject = async (
65
+ config: any,
66
+ command: string,
67
+ argv: string[],
68
+ result: string,
69
+ language: string
70
+ ) => {
71
+ const obfuscatedParams = obfuscateParams(argv)
72
+
73
+ const requestBody = {
74
+ event: TELEMETRY_CLI_COMMANDS_EVENT,
75
+ details: {
76
+ ip_address: '',
77
+ account_name: '',
78
+ account_host: '',
79
+ company_domain: '',
80
+ command: `contrast ${command} ${obfuscatedParams}`,
81
+ app_id:
82
+ config && config.applicationId
83
+ ? sha1Base64Value(config.applicationId)
84
+ : 'undefined',
85
+ project_id:
86
+ config && config.projectId
87
+ ? sha1Base64Value(config.projectId)
88
+ : 'undefined',
89
+ language: language,
90
+ result: result,
91
+ additional_info: '',
92
+ timestamp: new Date().toUTCString()
93
+ }
94
+ }
95
+
96
+ return await sendTelemetryRequest(config, requestBody)
97
+ }
98
+
99
+ export const sendTelemetryRequest = async (config: any, requestBody: any) => {
100
+ const client = getHttpClient(config)
101
+ return client
102
+ .postTelemetry(config, requestBody)
103
+ .then((res: any) => {
104
+ if (res.statusCode !== 200 && config.debug === true) {
105
+ console.log('Telemetry failed to send with status', res.statusCode)
106
+ }
107
+ return { statusCode: res.statusCode, statusMessage: res.statusMessage }
108
+ })
109
+ .catch((err: any) => {
110
+ return
111
+ })
112
+ }
113
+
114
+ export const obfuscateParams = (argv: string[]) => {
115
+ return argv
116
+ .join(' ')
117
+ .replace(/--(authorization [A-Z0-9]+)/gi, '--authorization *****')
118
+ .replace(/-(o [A-Z0-9-]+)/gi, '-o *****')
119
+ .replace(/--(organization-id [A-Z0-9-]+)/gi, '--organization-id *****')
120
+ .replace(/--(api-key [A-Z0-9]+)/gi, '--api-key *****')
121
+ }
122
+
123
+ export const paramExists = (
124
+ argv: string[],
125
+ param: string,
126
+ paramAlias: string | null
127
+ ) => {
128
+ return argv.find((arg: string) => arg === param || arg === paramAlias)
129
+ }
130
+
131
+ export const findParamValueFromArgs = (
132
+ argv: string[],
133
+ param: string,
134
+ paramAlias: string | null
135
+ ) => {
136
+ let paramAsValue
137
+
138
+ argv.forEach((arg: string, index: number) => {
139
+ if (
140
+ arg === param ||
141
+ (arg === paramAlias &&
142
+ argv[index + 1] !== undefined &&
143
+ argv[index + 1] !== null)
144
+ ) {
145
+ paramAsValue = argv[index + 1]
146
+ }
147
+ })
148
+
149
+ return paramAsValue
150
+ }
151
+
152
+ export const sha1Base64Value = (value: any) => {
153
+ return crypto.createHash('sha1').update(value).digest('base64')
154
+ }