emasser 3.4.0 → 3.4.1

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +8 -8
  3. data/.env-example +12 -12
  4. data/.github/release-drafter.yml +15 -15
  5. data/.github/workflows/codeql-analysis.yml +70 -70
  6. data/.github/workflows/draft-release.yml +15 -15
  7. data/.github/workflows/gh-pages.yml +32 -32
  8. data/.github/workflows/push-to-docker-mail.yml +28 -28
  9. data/.github/workflows/push-to-docker.yml +35 -35
  10. data/.github/workflows/release.yml +42 -42
  11. data/.github/workflows/rubocop.yml +23 -23
  12. data/.github/workflows/test-cli.yml +72 -72
  13. data/.gitignore +19 -19
  14. data/.mergify.yml +25 -25
  15. data/.rubocop.yml +80 -80
  16. data/.rubocop_todo.yml +27 -27
  17. data/CHANGELOG.md +16 -16
  18. data/Dockerfile +44 -44
  19. data/Gemfile +8 -8
  20. data/Gemfile.lock +104 -104
  21. data/LICENSE.md +15 -15
  22. data/README.md +178 -178
  23. data/Rakefile +18 -18
  24. data/_config.yml +1 -1
  25. data/docs/features.md +1436 -1436
  26. data/docs/redoc/index.html +1230 -1230
  27. data/emasser.gemspec +44 -44
  28. data/exe/emasser +5 -5
  29. data/lib/emasser/cli.rb +37 -37
  30. data/lib/emasser/configuration.rb +49 -49
  31. data/lib/emasser/constants.rb +26 -26
  32. data/lib/emasser/delete.rb +148 -148
  33. data/lib/emasser/errors.rb +14 -14
  34. data/lib/emasser/get.rb +949 -949
  35. data/lib/emasser/help/approvalCac_post_mapper.md +20 -20
  36. data/lib/emasser/help/approvalPac_post_mapper.md +20 -20
  37. data/lib/emasser/help/artifacts_del_mapper.md +9 -9
  38. data/lib/emasser/help/artifacts_post_mapper.md +59 -59
  39. data/lib/emasser/help/artifacts_put_mapper.md +34 -34
  40. data/lib/emasser/help/cloudresource_post_mapper.md +62 -62
  41. data/lib/emasser/help/cmmc_get_mapper.md +4 -4
  42. data/lib/emasser/help/container_post_mapper.md +44 -44
  43. data/lib/emasser/help/controls_put_mapper.md +74 -74
  44. data/lib/emasser/help/milestone_del_mapper.md +11 -11
  45. data/lib/emasser/help/milestone_post_mapper.md +14 -14
  46. data/lib/emasser/help/milestone_put_mapper.md +23 -23
  47. data/lib/emasser/help/poam_del_mapper.md +5 -5
  48. data/lib/emasser/help/poam_post_mapper.md +93 -93
  49. data/lib/emasser/help/poam_put_mapper.md +107 -107
  50. data/lib/emasser/help/staticcode_clear_mapper.md +16 -16
  51. data/lib/emasser/help/staticcode_post_mapper.md +21 -21
  52. data/lib/emasser/help/testresults_post_mapper.md +21 -21
  53. data/lib/emasser/help.rb +11 -11
  54. data/lib/emasser/input_converters.rb +21 -21
  55. data/lib/emasser/options_parser.rb +20 -20
  56. data/lib/emasser/output_converters.rb +111 -111
  57. data/lib/emasser/post.rb +830 -802
  58. data/lib/emasser/put.rb +588 -588
  59. data/lib/emasser/version.rb +5 -5
  60. data/lib/emasser.rb +19 -19
  61. metadata +8 -8
data/lib/emasser/put.rb CHANGED
@@ -1,588 +1,588 @@
1
- # frozen_string_literal: true
2
-
3
- # Hack class that properly formats the CLI help
4
- class SubCommandBase < Thor
5
- include OutputConverters
6
-
7
- # We do not control the method declaration for the banner
8
-
9
- # rubocop:disable Style/OptionalBooleanParameter
10
- def self.banner(command, _namespace = nil, subcommand = false)
11
- # Use the $thor_runner (declared by the Thor CLI framework)
12
- # to properly format the help text of sub-sub-commands.
13
-
14
- # rubocop:disable Style/GlobalVars
15
- if ancestors[0].to_s.include? '::Put'
16
- "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
17
- else
18
- "#{basename} put #{command.formatted_usage(self, $thor_runner, subcommand)}"
19
- end
20
- # rubocop:enable Style/GlobalVars
21
- end
22
- # rubocop:enable Style/OptionalBooleanParameter
23
- end
24
-
25
- # Override thor's long_desc identation behavior
26
- class Thor
27
- module Shell
28
- class Basic
29
- def print_wrapped(message, _options = {})
30
- message = "\n#{message}\n" unless message[0] == "\n"
31
- stdout.puts message
32
- end
33
- end
34
- end
35
- end
36
-
37
- module Emasser
38
- CONTROLS_PUT_HELP_MESSAGE = "\nInvoke \"bundle exec exe/emasser put controls help update\" for additional help"
39
- POAMS_PUT_HELP_MESSAGE = "\nInvoke \"bundle exec exe/emasser put poams help add\" for additional help"
40
- # Update Security Control information of a system for both the Implementation Plan and Risk Assessment.
41
- #
42
- # Endpoint:
43
- # /api/systems/{systemId}/controls - Update control information in a system for one or many controls
44
- # rubocop:disable Style/WordArray
45
- class Controls < SubCommandBase
46
- def self.exit_on_failure?
47
- true
48
- end
49
-
50
- desc 'update', 'Get control information in a system for one or many controls (acronym)'
51
- long_desc Help.text(:controls_put_mapper)
52
-
53
- # Required parameters/fields
54
- option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
55
- option :acronym, type: :string, required: true, desc: 'The system acronym(s) e.g "AC-1, AC-2"'
56
- option :responsibleEntities, type: :string, required: true,
57
- desc: 'Description of the responsible entities for the Security Control'
58
- option :controlDesignation, type: :string, required: true,
59
- enum: ['Common', 'System-Specific', 'Hybrid'],
60
- desc: 'The Security Control Designation'
61
- option :estimatedCompletionDate, type: :numeric, required: true, desc: 'Estimated completion date, Unix time format'
62
- option :implementationNarrative, type: :string, required: true, desc: 'Security control comments'
63
-
64
- # Conditional parameters/fields
65
- option :commonControlProvider,
66
- type: :string,
67
- required: false,
68
- enum: ['DoD', 'Component', 'Enclave'],
69
- desc: 'Indicate the type of Common Control Provider for an "Inherited" Security Control'
70
- option :naJustification,
71
- type: :string, required: false,
72
- desc: 'Provide justification for Security Controls deemed Not Applicable to the system'
73
- option :slcmCriticality,
74
- type: :string, required: false,
75
- desc: 'Criticality of Security Control regarding SLCM'
76
- option :slcmFrequency,
77
- type: :string, required: false,
78
- enum: ['Constantly', 'Daily', 'Weekly', 'Monthly', 'Quarterly', 'Semi-Annually',
79
- 'Annually', 'Every Two Years', 'Every Three Years', 'Undetermined'],
80
- desc: 'The System-Level Continuous Monitoring frequency'
81
- option :slcmMethod,
82
- type: :string, required: false,
83
- enum: ['Automated', 'Semi-Automated', 'Manual', 'Undetermined'],
84
- desc: 'The System-Level Continuous Monitoring method'
85
- option :slcmReporting,
86
- type: :string, required: false,
87
- desc: 'The System-Level Continuous Monitoring reporting'
88
- option :slcmTracking,
89
- type: :string, required: false,
90
- desc: 'The System-Level Continuous Monitoring tracking'
91
- option :slcmComments,
92
- type: :string, required: false,
93
- desc: 'Additional comments for Security Control regarding SLCM'
94
-
95
- # Optional parameters/fields
96
- option :implementationStatus,
97
- type: :string, required: false,
98
- enum: ['Planned', 'Implemented', 'Inherited', 'Not Applicable', 'Manually Inherited'],
99
- desc: 'Implementation status of the security control for the information system'
100
- option :severity,
101
- type: :string, required: false,
102
- enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
103
- desc: 'The security control severity, required for approved items'
104
- option :vulnerabiltySummary, type: :string, required: false, desc: 'The security control vulnerability summary'
105
- option :recommendations, type: :string, required: false, desc: 'The security control vulnerability recommendation'
106
- option :relevanceOfThreat,
107
- type: :string, required: false,
108
- enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
109
- desc: 'The security control vulnerability of threat'
110
- option :likelihood,
111
- type: :string, required: false,
112
- enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
113
- desc: 'The security control likelihood of vulnerability to threats'
114
- option :impact,
115
- type: :string, required: false,
116
- enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
117
- desc: 'The security control vulnerability impact'
118
- option :impactDescription, type: :string, required: false, desc: 'Description of the security control impact'
119
- option :residualRiskLevel,
120
- type: :string, required: false,
121
- enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
122
- desc: 'The security control risk level'
123
- option :testMethod,
124
- type: :string, required: false,
125
- enum: ['Test', 'Interview', 'Examine', 'Test, Interview', 'Test, Examine',
126
- 'Interview, Examine', 'Test, Interview, Examine'],
127
- desc: 'Assessment method/combination that determines if the security requirements are implemented correctly'
128
-
129
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
130
- def update
131
- # Required fields
132
- body = EmassClient::ControlsGet.new
133
- body.acronym = options[:acronym]
134
- body.responsible_entities = options[:responsibleEntities]
135
- body.control_designation = options[:controlDesignation]
136
- body.estimated_completion_date = options[:estimatedCompletionDate]
137
- body.implementation_narrative = options[:implementationNarrative]
138
-
139
- process_business_logic(body)
140
-
141
- # Add optional fields
142
- body.severity = options[:severity] if options[:severity]
143
- body.vulnerabilty_summary = options[:vulnerabiltySummary] if options[:vulnerabiltySummary]
144
- body.recommendations = options[:recommendations] if options[:recommendations]
145
- body.relevance_of_threat = options[:relevanceOfThreat] if options[:relevanceOfThreat]
146
- body.likelihood = options[:likelihood] if options[:likelihood]
147
- body.impact = options[:impact] if options[:impact]
148
- body.impact_description = options[:impactDescription] if options[:impactDescription]
149
- body.residual_risk_level = options[:residualRiskLevel] if options[:residualRiskLevel]
150
- body.test_method = options[:testMethod] if options[:testMethod]
151
-
152
- body_array = Array.new(1, body)
153
-
154
- begin
155
- result = EmassClient::ControlsApi.new.update_control_by_system_id(options[:systemId], body_array)
156
- puts to_output_hash(result).green
157
- rescue EmassClient::ApiError => e
158
- puts 'Exception when calling ControlsApi->update_control_by_system_id'.red
159
- puts to_output_hash(e)
160
- end
161
- end
162
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
163
-
164
- # rubocop:disable Style/CaseLikeIf, Style/StringLiterals, Metrics/BlockLength, Metrics/CyclomaticComplexity
165
- no_commands do
166
- # rubocop:disable Metrics/PerceivedComplexity, Style/GuardClause
167
- def process_business_logic(body)
168
- # Conditional fields based on implementationStatus content
169
- # unless executes code if conditional is false
170
- unless options[:implementationStatus].nil?
171
- body.implementation_status = options[:implementationStatus]
172
-
173
- if options[:implementationStatus] == "Planned" || options[:implementationStatus] == "Implemented"
174
- if options[:responsibleEntities].nil? || options[:slcmCriticality].nil? ||
175
- options[:slcmFrequency].nil? || options[:slcmMethod].nil? ||
176
- options[:slcmReporting].nil? || options[:slcmTracking].nil? || options[:slcmComments].nil?
177
- puts 'Missing one of these parameters/fields:'.red
178
- puts ' responsibleEntities, slcmCriticality, slcmFrequency,'.red
179
- puts ' slcmMethod,slcmReporting, slcmTracking, slcmComments'.red
180
- puts CONTROLS_PUT_HELP_MESSAGE.yellow
181
- exit
182
- else
183
- body.responsible_entities = options[:responsibleEntities]
184
- body.slcm_criticality = options[:slcmCriticality]
185
- body.slcm_frequency = options[:slcmFrequency]
186
- body.slcm_method = options[:slcmMethod]
187
- body.slcm_reporting = options[:slcmReporting]
188
- body.slcm_tracking = options[:slcmTracking]
189
- body.slcm_comments = options[:slcmComments]
190
- end
191
- elsif options[:implementationStatus] == 'Not Applicable'
192
- if options[:naJustification].nil? || options[:responsibleEntities].nil?
193
- puts 'Missing one of these parameters/fields:'.red
194
- puts ' naJustification, responsibleEntities'.red
195
- puts CONTROLS_PUT_HELP_MESSAGE.yellow
196
- exit
197
- else
198
- body.slcm_reporting = options[:naJustification]
199
- body.responsible_entities = options[:responsibleEntities]
200
- end
201
- elsif options[:implementationStatus] == 'Manually Inherited'
202
- if options[:commonControlProvider].nil? || options[:responsibleEntities].nil? ||
203
- options[:slcmCriticality].nil? || options[:slcmFrequency].nil? || options[:slcmMethod].nil? ||
204
- options[:slcmReporting].nil? || options[:slcmTracking].nil? || options[:slcmComments].nil?
205
- puts 'Missing one of these parameters/fields:'.red
206
- puts ' commonControlProvider, responsibleEntities, slcmCriticality,'.red
207
- puts ' slcmFrequency, slcmMethod, slcmReporting, slcmTracking, slcmComments'.red
208
- puts CONTROLS_PUT_HELP_MESSAGE.yellow
209
- exit
210
- else
211
- body.common_control_provider = options[:commonControlProvider]
212
- body.responsible_entities = options[:responsibleEntities]
213
- body.slcm_criticality = options[:slcmCriticality]
214
- body.slcm_frequency = options[:slcmFrequency]
215
- body.slcm_method = options[:slcmMethod]
216
- body.slcm_reporting = options[:slcmReporting]
217
- body.slcm_tracking = options[:slcmTracking]
218
- body.slcm_comments = options[:slcmComments]
219
- end
220
- elsif options[:implementationStatus] == 'Inherited'
221
- if options[:commonControlProvider].nil?
222
- puts 'When implementationStatus value is "Inherited" only the following fields are updated:'.red
223
- puts ' controlDesignation and commonControlProvider'.red
224
- puts 'Missing the commonControlProvider field'.red
225
- puts CONTROLS_PUT_HELP_MESSAGE.yellow
226
- exit
227
- else
228
- body.common_control_provider = options[:commonControlProvider]
229
- end
230
- end
231
- end
232
- end
233
- # rubocop:enable Metrics/PerceivedComplexity, Style/GuardClause
234
- end
235
- # rubocop:enable Style/CaseLikeIf, Style/StringLiterals, Metrics/BlockLength, Metrics/CyclomaticComplexity
236
- end
237
- # rubocop:enable Style/WordArray
238
-
239
- # Update Plan of Action (POA&M) items to a system.
240
- #
241
- # Endpoint:
242
- # /api/systems/{systemId}/poams - Update one or many poa&m items in a system
243
- class Poams < SubCommandBase
244
- def self.exit_on_failure?
245
- true
246
- end
247
-
248
- # Update a POAM -----------------------------------------------------------
249
- #
250
- # The following fields are required based on the contents of the status field
251
- # status Required Fields
252
- # -------------------------------------------------------------------------
253
- # Risk Accepted comments, resources
254
- # Ongoing scheduledCompletionDate, resources, milestones (at least 1)
255
- # Completed scheduledCompletionDate, comments, resources,
256
- # completionDate, milestones (at least 1)
257
- # Not Applicable POAM can not be created
258
- #--------------------------------------------------------------------------
259
- #
260
- # If a POC email is supplied, the application will attempt to locate a user
261
- # already registered within the application and pre-populate any information
262
- # not explicitly supplied in the request. If no such user is found, these
263
- # fields are required within the request.
264
- # pocOrganization, pocFirstName, pocLastName, pocEmail, pocPhoneNumber
265
-
266
- desc 'update', 'Update one or many POA&M items in a system'
267
- long_desc Help.text(:poam_put_mapper)
268
-
269
- # Required parameters/fields
270
- option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
271
- option :poamId, type: :numeric, required: true, desc: 'A numeric value representing the poam identification'
272
- # option :displayPoamId,
273
- # type: :numeric, required: true,
274
- # desc: 'Globally unique identifier for individual POA&M Items, seen on the front-end as "ID"'
275
- option :status, type: :string, required: true, enum: ['Ongoing', 'Risk Accepted', 'Completed', 'Not Applicable']
276
- option :vulnerabilityDescription, type: :string, required: true, desc: 'POA&M vulnerability description'
277
- option :sourceIdentVuln,
278
- type: :string, required: true, desc: 'Source that identifies the vulnerability'
279
- option :pocOrganization, type: :string, required: true, desc: 'Organization/Office represented'
280
- option :resources, type: :string, required: true, desc: 'List of resources used'
281
-
282
- # Conditional parameters/fields
283
- option :milestone,
284
- type: :hash, required: false, desc: 'key:values are: milestoneId, description and scheduledCompletionDate'
285
- option :pocFirstName, type: :string, required: false, desc: 'First name of POC'
286
- option :pocLastName, type: :string, required: false, desc: 'Last name of POC.'
287
- option :pocEmail, type: :string, required: false, desc: 'Email address of POC'
288
- option :pocPhoneNumber, type: :string, required: false, desc: 'Phone number of POC (area code) ***-**** format'
289
- option :severity, type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
290
- option :scheduledCompletionDate,
291
- type: :numeric, required: false, desc: 'The scheduled completion date - Unix time format'
292
- option :completionDate,
293
- type: :numeric, required: false, desc: 'The schedule completion date - Unix time format'
294
- option :comments, type: :string, required: false, desc: 'Comments for completed and risk accepted POA&M items'
295
- option :isActive, type: :boolean, required: false, default: false, desc: 'BOOLEAN - true or false.'
296
-
297
- # Optional parameters/fields
298
- option :externalUid, type: :string, required: false, desc: 'External ID associated with the POA&M'
299
- option :controlAcronym, type: :string, required: false, desc: 'The system acronym(s) e.g "AC-1, AC-2"'
300
- option :cci, type: :string, required: false, desc: 'The system CCIS string numerical value'
301
- option :securityChecks, type: :string, required: false, desc: 'Security Checks that are associated with the POA&M'
302
- option :rawSeverity, type: :string, required: false, enum: %w[I II III]
303
- option :relevanceOfThreat,
304
- type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
305
- option :likelihood, type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
306
- option :impact, type: :string, required: false, desc: 'Description of Security Control’s impact'
307
- option :impactDescription, type: :string, required: false, desc: 'Description of the security control impact'
308
- option :residualRiskLevel,
309
- type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
310
- option :recommendations, type: :string, required: false, desc: 'Recomendations'
311
- option :mitigation, type: :string, required: false, desc: 'Mitigation explanation'
312
-
313
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
314
- def update
315
- # Required fields
316
- body = EmassClient::PoamGet.new
317
- body.poam_id = options[:poamId]
318
- body.status = options[:status]
319
- body.vulnerability_description = options[:vulnerabilityDescription]
320
- body.source_ident_vuln = options[:sourceIdentVuln]
321
- body.poc_organization = options[:pocOrganization]
322
- body.resources = options[:resources]
323
-
324
- process_business_logic(body)
325
-
326
- # Add conditional fields
327
- body.poc_first_name = options[:pocFirstName] if options[:pocFirstName]
328
- body.poc_last_name = options[:pocLastName] if options[:pocLastName]
329
- body.poc_email = options[:pocEmail] if options[:pocEmail]
330
- body.poc_phone_number = options[:pocPhoneNumber] if options[:pocPhoneNumber]
331
- body.severity = options[:severity] if options[:severity]
332
-
333
- # Add optional fields
334
- body.external_uid = options[:externalUid] if options[:externalUid]
335
- body.control_acronym = options[:controlAcronym] if options[:controlAcronym]
336
- body.cci = options[:cci] if options[:cci]
337
- body.security_checks = options[:securityChecks] if options[:securityChecks]
338
- body.raw_severity = options[:rawSeverity] if options[:rawSeverity]
339
- body.relevance_of_threat = options[:relevanceOfThreat] if options[:relevanceOfThreat]
340
- body.likelihood = options[:likelihood] if options[:likelihood]
341
- body.impact = options[:impact] if options[:impact]
342
- body.impact_description = options[:impactDescription] if options[:impactDescription]
343
- body.residual_risk_level = options[:residualRiskLevel] if options[:residualRiskLevel]
344
- body.recommendations = options[:recommendations] if options[:recommendations]
345
- body.mitigation = options[:mitigation] if options[:mitigation]
346
-
347
- body_array = Array.new(1, body)
348
-
349
- begin
350
- result = EmassClient::POAMApi.new.update_poam_by_system_id(options[:systemId], body_array)
351
- puts to_output_hash(result).green
352
- rescue EmassClient::ApiError => e
353
- puts 'Exception when calling POAMApi->update_poam_by_system_id'.red
354
- puts to_output_hash(e)
355
- end
356
- end
357
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
358
-
359
- # rubocop:disable Metrics/AbcSize, Metrics/BlockLength, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
360
- no_commands do
361
- def process_business_logic(body)
362
- #-----------------------------------------------------------------------------
363
- # Conditional fields based on the status field values
364
- # "Risk Accepted" comments, resources
365
- # "Ongoing" scheduledCompletionDate, resources, milestones (at least 1)
366
- # "Completed" scheduledCompletionDate, comments, resources,
367
- # completionDate, milestones (at least 1)
368
- # "Not Applicable" POAM can not be created
369
- #-----------------------------------------------------------------------------
370
- # rubocop:disable Style/CaseLikeIf, Style/StringLiterals
371
- if options[:status] == "Risk Accepted"
372
- if options[:comments].nil?
373
- puts 'When status = "Risk Accepted" the following parameters/fields are required:'.red
374
- puts ' comments'.red
375
- puts POAMS_PUT_HELP_MESSAGE.yellow
376
- exit
377
- elsif !(options[:scheduledCompletionDate].nil? && options[:milestone].nil?)
378
- puts 'When status = "Risk Accepted" POA&M Item CAN NOT be saved with the following parameters/fields:'.red
379
- puts ' scheduledCompletionDate, or milestone'.red
380
- puts POAMS_PUT_HELP_MESSAGE.yellow
381
- exit
382
- else
383
- body.comments = options[:comments]
384
- end
385
- elsif options[:status] == "Ongoing"
386
- if options[:scheduledCompletionDate].nil? || options[:milestone].nil?
387
- puts 'When status = "Ongoing" the following parameters/fields are required:'.red
388
- puts ' scheduledCompletionDate, milestone'.red
389
- print_milestone_help
390
- puts POAMS_PUT_HELP_MESSAGE.yellow
391
- exit
392
- elsif options[:milestone]["description"].nil? || options[:milestone]["scheduledCompletionDate"].nil?
393
- puts 'Missing milstone parameters/fields'.red
394
- print_milestone_help
395
- exit
396
- else
397
- body.scheduled_completion_date = options[:scheduledCompletionDate]
398
-
399
- milestone = EmassClient::MilestonesRequiredPut.new
400
- milestone.milestone_id = options[:milestone]["milestoneId"] if options[:milestone]["milestoneId"]
401
- milestone.description = options[:milestone]["description"]
402
- milestone.scheduled_completion_date = options[:milestone]["scheduledCompletionDate"]
403
- milestone_array = Array.new(1, milestone)
404
- body.milestones = milestone_array
405
- end
406
- elsif options[:status] == "Completed"
407
- if options[:scheduledCompletionDate].nil? || options[:comments].nil? ||
408
- options[:completionDate].nil? || options[:milestone].nil?
409
- puts 'Missing one of these parameters/fields:'.red
410
- puts ' scheduledCompletionDate, comments, completionDate, or milestone'.red
411
- print_milestone_help
412
- puts POAMS_PUT_HELP_MESSAGE.yellow
413
- exit
414
- else
415
- body.scheduled_completion_date = options[:scheduledCompletionDate]
416
- body.comments = options[:comments]
417
- body.completion_date = options[:completionDate]
418
-
419
- milestone = EmassClient::MilestonesRequiredPut.new
420
- milestone.milestone_id = options[:milestone]["milestoneId"] if options[:milestone]["milestoneId"]
421
- milestone.description = options[:milestone]["description"]
422
- milestone.scheduled_completion_date = options[:milestone]["scheduledCompletionDate"]
423
- milestone_array = Array.new(1, milestone)
424
- body.milestones = milestone_array
425
- end
426
- end
427
-
428
- # POC checks: If any poc information is provided all POC fields are required
429
- if options[:pocFirstName]
430
- if options[:pocLastName].nil? || options[:pocEmail].nil? || options[:pocPhoneNumber].nil?
431
- puts 'If a POC first name is given, then all POC information must be entered:'.red
432
- puts ' pocLastName, pocEmail, pocPhoneNumber'.red
433
- puts POAMS_PUT_HELP_MESSAGE.yellow
434
- exit
435
- end
436
- elsif options[:pocLastName]
437
- if options[:pocFirstName].nil? || options[:pocEmail].nil? || options[:pocPhoneNumber].nil?
438
- puts 'If a POC last name is given, then all POC information must be entered:'.red
439
- puts ' pocFirstName, pocEmail, pocPhoneNumber'.red
440
- puts POAMS_PUT_HELP_MESSAGE.yellow
441
- exit
442
- end
443
- elsif options[:pocEmail]
444
- if options[:pocFirstName].nil? || options[:pocLastName].nil? || options[:pocPhoneNumber].nil?
445
- puts 'If a POC email is given, then all POC information must be entered:'.red
446
- puts ' pocFirstName, pocLastName, pocPhoneNumber'.red
447
- puts POAMS_PUT_HELP_MESSAGE.yellow
448
- exit
449
- end
450
- elsif options[:pocPhoneNumber]
451
- if options[:pocFirstName].nil? || options[:pocLastName].nil? || options[:pocEmail].nil?
452
- puts 'If a POC phone number is given, then all POC information must be entered:'.red
453
- puts ' pocFirstName, pocLastName, pocEmail'.red
454
- puts POAMS_PUT_HELP_MESSAGE.yellow
455
- exit
456
- end
457
- end
458
- # rubocop:enable Style/CaseLikeIf, Style/StringLiterals
459
- end
460
-
461
- def print_milestone_help
462
- puts 'Milestone format is:'.yellow
463
- puts ' --milestone milestoneId:[value] description:"[value]" scheduledCompletionDate:"[value]"'.yellow
464
- puts 'The milestoneId:[value] is optional, if not provided a new milestone is created'.yellow
465
- end
466
- end
467
- # rubocop:enable Metrics/AbcSize, Metrics/BlockLength, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
468
- end
469
-
470
- # Update Milestones items to a system.
471
- #
472
- # Endpoint:
473
- # /api/systems/{systemId}/poams/{poamId}/milestones - Update milestones in one or many poa&m items in a system
474
- class Milestones < SubCommandBase
475
- def self.exit_on_failure?
476
- true
477
- end
478
-
479
- desc 'update', 'Update milestone(s) for given specified system and poam'
480
- long_desc Help.text(:milestone_put_mapper)
481
-
482
- # Required parameters/fields
483
- option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
484
- option :poamId, type: :numeric, required: true, desc: 'A numeric value representing the poam identification'
485
- option :milestoneId,
486
- type: :numeric, required: true, desc: 'A numeric value representing the milestone identification'
487
- option :description, type: :string, required: true, desc: 'The milestone description'
488
- option :scheduledCompletionDate,
489
- type: :numeric, required: false, desc: 'The scheduled completion date - Unix time format'
490
-
491
- def update
492
- body = EmassClient::MilestonesGet.new
493
- body.milestone_id = options[:milestoneId]
494
- body.description = options[:description]
495
- body.scheduled_completion_date = options[:scheduledCompletionDate]
496
- body_array = Array.new(1, body)
497
-
498
- begin
499
- # Get milestones in one or many poa&m items in a system
500
- result = EmassClient::MilestonesApi
501
- .new
502
- .update_milestone_by_system_id_and_poam_id(options[:systemId], options[:poamId], body_array)
503
- puts to_output_hash(result).green
504
- rescue EmassClient::ApiError => e
505
- puts 'Exception when calling MilestonesApi->update_milestone_by_system_id_and_poam_id'.red
506
- puts to_output_hash(e)
507
- end
508
- end
509
- end
510
-
511
- # Update one or many artifacts for a system (this implementation only updates one artifact per each execution)
512
- #
513
- # Endpoint:
514
- # /api/systems/{systemId}/artifacts - Put (update) one or many artifacts for a system
515
- class Artifacts < SubCommandBase
516
- def self.exit_on_failure?
517
- true
518
- end
519
-
520
- desc 'update', 'Updates artifacts for a system with provided entries'
521
- long_desc Help.text(:artifacts_put_mapper)
522
-
523
- # Required parameters/fields
524
- option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
525
- option :filename, type: :string, required: true, desc: 'Artifact file name to be updated'
526
- option :type,
527
- type: :string, required: true,
528
- enum: ['Procedure', 'Diagram', 'Policy', 'Labor', 'Document',
529
- 'Image', 'Other', 'Scan Result', 'Auditor Report']
530
- option :category, type: :string, required: true, enum: ['Implementation Guidance', 'Evidence']
531
- option :isTemplate, type: :boolean, required: false, default: false, desc: 'BOOLEAN - true or false.'
532
- # NOTE: compress is a required parameter, however Thor does not allow a boolean type to be required because it
533
- # automatically creates a --no-isTemplate option for isTemplate=false
534
-
535
- # Optional fields
536
- option :description, type: :string, required: false, desc: 'Artifact description'
537
- option :refPageNumber, type: :string, required: false, desc: 'Artifact reference page number'
538
- option :ccis, type: :string, required: false, desc: 'The system CCIs string numerical value'
539
- option :controls,
540
- type: :string, required: false,
541
- desc: 'Control acronym associated with the artifact. NIST SP 800-53 Revision 4 defined'
542
- option :artifactExpirationDate,
543
- type: :numeric, required: false, desc: 'Date Artifact expires and requires review - Unix time format'
544
- option :lastReviewedDate,
545
- type: :numeric, required: false, desc: 'Date Artifact was last reviewed - Unix time format'
546
-
547
- # rubocop:disable Metrics/CyclomaticComplexity
548
- def update
549
- body = EmassClient::ArtifactsGet.new
550
- body.filename = options[:filename]
551
- body.type = options[:type]
552
- body.category = options[:category]
553
- body.is_template = options[:isTemplate]
554
- # Optional fields
555
- body.description = options[:description] if options[:description]
556
- body.ref_page_number = options[:refPageNumber] if options[:refPageNumber]
557
- body.ccis = options[:ccis] if options[:ccis]
558
- body.controls = options[:controls] if options[:controls]
559
- body.artifact_expiration_date = options[:artifactExpirationDate] if options[:artifactExpirationDate]
560
- body.last_reviewed_date = options[:lastReviewedDate] if options[:lastReviewedDate]
561
-
562
- body_array = Array.new(1, body)
563
-
564
- begin
565
- result = EmassClient::ArtifactsApi.new.update_artifact_by_system_id(options[:systemId], body_array)
566
- puts to_output_hash(result).green
567
- rescue EmassClient::ApiError => e
568
- puts 'Exception when calling ArtifactsApi->update_artifact_by_system_id'.red
569
- puts to_output_hash(e)
570
- end
571
- end
572
- # rubocop:enable Metrics/CyclomaticComplexity
573
- end
574
-
575
- class Put < SubCommandBase
576
- desc 'controls', 'Update system Controls'
577
- subcommand 'controls', Controls
578
-
579
- desc 'poams', 'Update Plan of Action (POA&M) items for a system'
580
- subcommand 'poams', Poams
581
-
582
- desc 'milestones', 'Update Milestones items for a system'
583
- subcommand 'milestones', Milestones
584
-
585
- desc 'artifacts', 'Put system Artifacts'
586
- subcommand 'artifacts', Artifacts
587
- end
588
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Hack class that properly formats the CLI help
4
+ class SubCommandBase < Thor
5
+ include OutputConverters
6
+
7
+ # We do not control the method declaration for the banner
8
+
9
+ # rubocop:disable Style/OptionalBooleanParameter
10
+ def self.banner(command, _namespace = nil, subcommand = false)
11
+ # Use the $thor_runner (declared by the Thor CLI framework)
12
+ # to properly format the help text of sub-sub-commands.
13
+
14
+ # rubocop:disable Style/GlobalVars
15
+ if ancestors[0].to_s.include? '::Put'
16
+ "#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
17
+ else
18
+ "#{basename} put #{command.formatted_usage(self, $thor_runner, subcommand)}"
19
+ end
20
+ # rubocop:enable Style/GlobalVars
21
+ end
22
+ # rubocop:enable Style/OptionalBooleanParameter
23
+ end
24
+
25
+ # Override thor's long_desc identation behavior
26
+ class Thor
27
+ module Shell
28
+ class Basic
29
+ def print_wrapped(message, _options = {})
30
+ message = "\n#{message}\n" unless message[0] == "\n"
31
+ stdout.puts message
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ module Emasser
38
+ CONTROLS_PUT_HELP_MESSAGE = "\nInvoke \"bundle exec exe/emasser put controls help update\" for additional help"
39
+ POAMS_PUT_HELP_MESSAGE = "\nInvoke \"bundle exec exe/emasser put poams help add\" for additional help"
40
+ # Update Security Control information of a system for both the Implementation Plan and Risk Assessment.
41
+ #
42
+ # Endpoint:
43
+ # /api/systems/{systemId}/controls - Update control information in a system for one or many controls
44
+ # rubocop:disable Style/WordArray
45
+ class Controls < SubCommandBase
46
+ def self.exit_on_failure?
47
+ true
48
+ end
49
+
50
+ desc 'update', 'Get control information in a system for one or many controls (acronym)'
51
+ long_desc Help.text(:controls_put_mapper)
52
+
53
+ # Required parameters/fields
54
+ option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
55
+ option :acronym, type: :string, required: true, desc: 'The system acronym(s) e.g "AC-1, AC-2"'
56
+ option :responsibleEntities, type: :string, required: true,
57
+ desc: 'Description of the responsible entities for the Security Control'
58
+ option :controlDesignation, type: :string, required: true,
59
+ enum: ['Common', 'System-Specific', 'Hybrid'],
60
+ desc: 'The Security Control Designation'
61
+ option :estimatedCompletionDate, type: :numeric, required: true, desc: 'Estimated completion date, Unix time format'
62
+ option :implementationNarrative, type: :string, required: true, desc: 'Security control comments'
63
+
64
+ # Conditional parameters/fields
65
+ option :commonControlProvider,
66
+ type: :string,
67
+ required: false,
68
+ enum: ['DoD', 'Component', 'Enclave'],
69
+ desc: 'Indicate the type of Common Control Provider for an "Inherited" Security Control'
70
+ option :naJustification,
71
+ type: :string, required: false,
72
+ desc: 'Provide justification for Security Controls deemed Not Applicable to the system'
73
+ option :slcmCriticality,
74
+ type: :string, required: false,
75
+ desc: 'Criticality of Security Control regarding SLCM'
76
+ option :slcmFrequency,
77
+ type: :string, required: false,
78
+ enum: ['Constantly', 'Daily', 'Weekly', 'Monthly', 'Quarterly', 'Semi-Annually',
79
+ 'Annually', 'Every Two Years', 'Every Three Years', 'Undetermined'],
80
+ desc: 'The System-Level Continuous Monitoring frequency'
81
+ option :slcmMethod,
82
+ type: :string, required: false,
83
+ enum: ['Automated', 'Semi-Automated', 'Manual', 'Undetermined'],
84
+ desc: 'The System-Level Continuous Monitoring method'
85
+ option :slcmReporting,
86
+ type: :string, required: false,
87
+ desc: 'The System-Level Continuous Monitoring reporting'
88
+ option :slcmTracking,
89
+ type: :string, required: false,
90
+ desc: 'The System-Level Continuous Monitoring tracking'
91
+ option :slcmComments,
92
+ type: :string, required: false,
93
+ desc: 'Additional comments for Security Control regarding SLCM'
94
+
95
+ # Optional parameters/fields
96
+ option :implementationStatus,
97
+ type: :string, required: false,
98
+ enum: ['Planned', 'Implemented', 'Inherited', 'Not Applicable', 'Manually Inherited'],
99
+ desc: 'Implementation status of the security control for the information system'
100
+ option :severity,
101
+ type: :string, required: false,
102
+ enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
103
+ desc: 'The security control severity, required for approved items'
104
+ option :vulnerabiltySummary, type: :string, required: false, desc: 'The security control vulnerability summary'
105
+ option :recommendations, type: :string, required: false, desc: 'The security control vulnerability recommendation'
106
+ option :relevanceOfThreat,
107
+ type: :string, required: false,
108
+ enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
109
+ desc: 'The security control vulnerability of threat'
110
+ option :likelihood,
111
+ type: :string, required: false,
112
+ enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
113
+ desc: 'The security control likelihood of vulnerability to threats'
114
+ option :impact,
115
+ type: :string, required: false,
116
+ enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
117
+ desc: 'The security control vulnerability impact'
118
+ option :impactDescription, type: :string, required: false, desc: 'Description of the security control impact'
119
+ option :residualRiskLevel,
120
+ type: :string, required: false,
121
+ enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High'],
122
+ desc: 'The security control risk level'
123
+ option :testMethod,
124
+ type: :string, required: false,
125
+ enum: ['Test', 'Interview', 'Examine', 'Test, Interview', 'Test, Examine',
126
+ 'Interview, Examine', 'Test, Interview, Examine'],
127
+ desc: 'Assessment method/combination that determines if the security requirements are implemented correctly'
128
+
129
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
130
+ def update
131
+ # Required fields
132
+ body = EmassClient::ControlsGet.new
133
+ body.acronym = options[:acronym]
134
+ body.responsible_entities = options[:responsibleEntities]
135
+ body.control_designation = options[:controlDesignation]
136
+ body.estimated_completion_date = options[:estimatedCompletionDate]
137
+ body.implementation_narrative = options[:implementationNarrative]
138
+
139
+ process_business_logic(body)
140
+
141
+ # Add optional fields
142
+ body.severity = options[:severity] if options[:severity]
143
+ body.vulnerabilty_summary = options[:vulnerabiltySummary] if options[:vulnerabiltySummary]
144
+ body.recommendations = options[:recommendations] if options[:recommendations]
145
+ body.relevance_of_threat = options[:relevanceOfThreat] if options[:relevanceOfThreat]
146
+ body.likelihood = options[:likelihood] if options[:likelihood]
147
+ body.impact = options[:impact] if options[:impact]
148
+ body.impact_description = options[:impactDescription] if options[:impactDescription]
149
+ body.residual_risk_level = options[:residualRiskLevel] if options[:residualRiskLevel]
150
+ body.test_method = options[:testMethod] if options[:testMethod]
151
+
152
+ body_array = Array.new(1, body)
153
+
154
+ begin
155
+ result = EmassClient::ControlsApi.new.update_control_by_system_id(options[:systemId], body_array)
156
+ puts to_output_hash(result).green
157
+ rescue EmassClient::ApiError => e
158
+ puts 'Exception when calling ControlsApi->update_control_by_system_id'.red
159
+ puts to_output_hash(e)
160
+ end
161
+ end
162
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
163
+
164
+ # rubocop:disable Style/CaseLikeIf, Style/StringLiterals, Metrics/BlockLength, Metrics/CyclomaticComplexity
165
+ no_commands do
166
+ # rubocop:disable Metrics/PerceivedComplexity, Style/GuardClause
167
+ def process_business_logic(body)
168
+ # Conditional fields based on implementationStatus content
169
+ # unless executes code if conditional is false
170
+ unless options[:implementationStatus].nil?
171
+ body.implementation_status = options[:implementationStatus]
172
+
173
+ if options[:implementationStatus] == "Planned" || options[:implementationStatus] == "Implemented"
174
+ if options[:responsibleEntities].nil? || options[:slcmCriticality].nil? ||
175
+ options[:slcmFrequency].nil? || options[:slcmMethod].nil? ||
176
+ options[:slcmReporting].nil? || options[:slcmTracking].nil? || options[:slcmComments].nil?
177
+ puts 'Missing one of these parameters/fields:'.red
178
+ puts ' responsibleEntities, slcmCriticality, slcmFrequency,'.red
179
+ puts ' slcmMethod,slcmReporting, slcmTracking, slcmComments'.red
180
+ puts CONTROLS_PUT_HELP_MESSAGE.yellow
181
+ exit
182
+ else
183
+ body.responsible_entities = options[:responsibleEntities]
184
+ body.slcm_criticality = options[:slcmCriticality]
185
+ body.slcm_frequency = options[:slcmFrequency]
186
+ body.slcm_method = options[:slcmMethod]
187
+ body.slcm_reporting = options[:slcmReporting]
188
+ body.slcm_tracking = options[:slcmTracking]
189
+ body.slcm_comments = options[:slcmComments]
190
+ end
191
+ elsif options[:implementationStatus] == 'Not Applicable'
192
+ if options[:naJustification].nil? || options[:responsibleEntities].nil?
193
+ puts 'Missing one of these parameters/fields:'.red
194
+ puts ' naJustification, responsibleEntities'.red
195
+ puts CONTROLS_PUT_HELP_MESSAGE.yellow
196
+ exit
197
+ else
198
+ body.slcm_reporting = options[:naJustification]
199
+ body.responsible_entities = options[:responsibleEntities]
200
+ end
201
+ elsif options[:implementationStatus] == 'Manually Inherited'
202
+ if options[:commonControlProvider].nil? || options[:responsibleEntities].nil? ||
203
+ options[:slcmCriticality].nil? || options[:slcmFrequency].nil? || options[:slcmMethod].nil? ||
204
+ options[:slcmReporting].nil? || options[:slcmTracking].nil? || options[:slcmComments].nil?
205
+ puts 'Missing one of these parameters/fields:'.red
206
+ puts ' commonControlProvider, responsibleEntities, slcmCriticality,'.red
207
+ puts ' slcmFrequency, slcmMethod, slcmReporting, slcmTracking, slcmComments'.red
208
+ puts CONTROLS_PUT_HELP_MESSAGE.yellow
209
+ exit
210
+ else
211
+ body.common_control_provider = options[:commonControlProvider]
212
+ body.responsible_entities = options[:responsibleEntities]
213
+ body.slcm_criticality = options[:slcmCriticality]
214
+ body.slcm_frequency = options[:slcmFrequency]
215
+ body.slcm_method = options[:slcmMethod]
216
+ body.slcm_reporting = options[:slcmReporting]
217
+ body.slcm_tracking = options[:slcmTracking]
218
+ body.slcm_comments = options[:slcmComments]
219
+ end
220
+ elsif options[:implementationStatus] == 'Inherited'
221
+ if options[:commonControlProvider].nil?
222
+ puts 'When implementationStatus value is "Inherited" only the following fields are updated:'.red
223
+ puts ' controlDesignation and commonControlProvider'.red
224
+ puts 'Missing the commonControlProvider field'.red
225
+ puts CONTROLS_PUT_HELP_MESSAGE.yellow
226
+ exit
227
+ else
228
+ body.common_control_provider = options[:commonControlProvider]
229
+ end
230
+ end
231
+ end
232
+ end
233
+ # rubocop:enable Metrics/PerceivedComplexity, Style/GuardClause
234
+ end
235
+ # rubocop:enable Style/CaseLikeIf, Style/StringLiterals, Metrics/BlockLength, Metrics/CyclomaticComplexity
236
+ end
237
+ # rubocop:enable Style/WordArray
238
+
239
+ # Update Plan of Action (POA&M) items to a system.
240
+ #
241
+ # Endpoint:
242
+ # /api/systems/{systemId}/poams - Update one or many poa&m items in a system
243
+ class Poams < SubCommandBase
244
+ def self.exit_on_failure?
245
+ true
246
+ end
247
+
248
+ # Update a POAM -----------------------------------------------------------
249
+ #
250
+ # The following fields are required based on the contents of the status field
251
+ # status Required Fields
252
+ # -------------------------------------------------------------------------
253
+ # Risk Accepted comments, resources
254
+ # Ongoing scheduledCompletionDate, resources, milestones (at least 1)
255
+ # Completed scheduledCompletionDate, comments, resources,
256
+ # completionDate, milestones (at least 1)
257
+ # Not Applicable POAM can not be created
258
+ #--------------------------------------------------------------------------
259
+ #
260
+ # If a POC email is supplied, the application will attempt to locate a user
261
+ # already registered within the application and pre-populate any information
262
+ # not explicitly supplied in the request. If no such user is found, these
263
+ # fields are required within the request.
264
+ # pocOrganization, pocFirstName, pocLastName, pocEmail, pocPhoneNumber
265
+
266
+ desc 'update', 'Update one or many POA&M items in a system'
267
+ long_desc Help.text(:poam_put_mapper)
268
+
269
+ # Required parameters/fields
270
+ option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
271
+ option :poamId, type: :numeric, required: true, desc: 'A numeric value representing the poam identification'
272
+ # option :displayPoamId,
273
+ # type: :numeric, required: true,
274
+ # desc: 'Globally unique identifier for individual POA&M Items, seen on the front-end as "ID"'
275
+ option :status, type: :string, required: true, enum: ['Ongoing', 'Risk Accepted', 'Completed', 'Not Applicable']
276
+ option :vulnerabilityDescription, type: :string, required: true, desc: 'POA&M vulnerability description'
277
+ option :sourceIdentVuln,
278
+ type: :string, required: true, desc: 'Source that identifies the vulnerability'
279
+ option :pocOrganization, type: :string, required: true, desc: 'Organization/Office represented'
280
+ option :resources, type: :string, required: true, desc: 'List of resources used'
281
+
282
+ # Conditional parameters/fields
283
+ option :milestone,
284
+ type: :hash, required: false, desc: 'key:values are: milestoneId, description and scheduledCompletionDate'
285
+ option :pocFirstName, type: :string, required: false, desc: 'First name of POC'
286
+ option :pocLastName, type: :string, required: false, desc: 'Last name of POC.'
287
+ option :pocEmail, type: :string, required: false, desc: 'Email address of POC'
288
+ option :pocPhoneNumber, type: :string, required: false, desc: 'Phone number of POC (area code) ***-**** format'
289
+ option :severity, type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
290
+ option :scheduledCompletionDate,
291
+ type: :numeric, required: false, desc: 'The scheduled completion date - Unix time format'
292
+ option :completionDate,
293
+ type: :numeric, required: false, desc: 'The schedule completion date - Unix time format'
294
+ option :comments, type: :string, required: false, desc: 'Comments for completed and risk accepted POA&M items'
295
+ option :isActive, type: :boolean, required: false, default: false, desc: 'BOOLEAN - true or false.'
296
+
297
+ # Optional parameters/fields
298
+ option :externalUid, type: :string, required: false, desc: 'External ID associated with the POA&M'
299
+ option :controlAcronym, type: :string, required: false, desc: 'The system acronym(s) e.g "AC-1, AC-2"'
300
+ option :cci, type: :string, required: false, desc: 'The system CCIS string numerical value'
301
+ option :securityChecks, type: :string, required: false, desc: 'Security Checks that are associated with the POA&M'
302
+ option :rawSeverity, type: :string, required: false, enum: %w[I II III]
303
+ option :relevanceOfThreat,
304
+ type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
305
+ option :likelihood, type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
306
+ option :impact, type: :string, required: false, desc: 'Description of Security Control’s impact'
307
+ option :impactDescription, type: :string, required: false, desc: 'Description of the security control impact'
308
+ option :residualRiskLevel,
309
+ type: :string, required: false, enum: ['Very Low', 'Low', 'Moderate', 'High', 'Very High']
310
+ option :recommendations, type: :string, required: false, desc: 'Recomendations'
311
+ option :mitigation, type: :string, required: false, desc: 'Mitigation explanation'
312
+
313
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
314
+ def update
315
+ # Required fields
316
+ body = EmassClient::PoamGet.new
317
+ body.poam_id = options[:poamId]
318
+ body.status = options[:status]
319
+ body.vulnerability_description = options[:vulnerabilityDescription]
320
+ body.source_ident_vuln = options[:sourceIdentVuln]
321
+ body.poc_organization = options[:pocOrganization]
322
+ body.resources = options[:resources]
323
+
324
+ process_business_logic(body)
325
+
326
+ # Add conditional fields
327
+ body.poc_first_name = options[:pocFirstName] if options[:pocFirstName]
328
+ body.poc_last_name = options[:pocLastName] if options[:pocLastName]
329
+ body.poc_email = options[:pocEmail] if options[:pocEmail]
330
+ body.poc_phone_number = options[:pocPhoneNumber] if options[:pocPhoneNumber]
331
+ body.severity = options[:severity] if options[:severity]
332
+
333
+ # Add optional fields
334
+ body.external_uid = options[:externalUid] if options[:externalUid]
335
+ body.control_acronym = options[:controlAcronym] if options[:controlAcronym]
336
+ body.cci = options[:cci] if options[:cci]
337
+ body.security_checks = options[:securityChecks] if options[:securityChecks]
338
+ body.raw_severity = options[:rawSeverity] if options[:rawSeverity]
339
+ body.relevance_of_threat = options[:relevanceOfThreat] if options[:relevanceOfThreat]
340
+ body.likelihood = options[:likelihood] if options[:likelihood]
341
+ body.impact = options[:impact] if options[:impact]
342
+ body.impact_description = options[:impactDescription] if options[:impactDescription]
343
+ body.residual_risk_level = options[:residualRiskLevel] if options[:residualRiskLevel]
344
+ body.recommendations = options[:recommendations] if options[:recommendations]
345
+ body.mitigation = options[:mitigation] if options[:mitigation]
346
+
347
+ body_array = Array.new(1, body)
348
+
349
+ begin
350
+ result = EmassClient::POAMApi.new.update_poam_by_system_id(options[:systemId], body_array)
351
+ puts to_output_hash(result).green
352
+ rescue EmassClient::ApiError => e
353
+ puts 'Exception when calling POAMApi->update_poam_by_system_id'.red
354
+ puts to_output_hash(e)
355
+ end
356
+ end
357
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
358
+
359
+ # rubocop:disable Metrics/AbcSize, Metrics/BlockLength, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
360
+ no_commands do
361
+ def process_business_logic(body)
362
+ #-----------------------------------------------------------------------------
363
+ # Conditional fields based on the status field values
364
+ # "Risk Accepted" comments, resources
365
+ # "Ongoing" scheduledCompletionDate, resources, milestones (at least 1)
366
+ # "Completed" scheduledCompletionDate, comments, resources,
367
+ # completionDate, milestones (at least 1)
368
+ # "Not Applicable" POAM can not be created
369
+ #-----------------------------------------------------------------------------
370
+ # rubocop:disable Style/CaseLikeIf, Style/StringLiterals
371
+ if options[:status] == "Risk Accepted"
372
+ if options[:comments].nil?
373
+ puts 'When status = "Risk Accepted" the following parameters/fields are required:'.red
374
+ puts ' comments'.red
375
+ puts POAMS_PUT_HELP_MESSAGE.yellow
376
+ exit
377
+ elsif !(options[:scheduledCompletionDate].nil? && options[:milestone].nil?)
378
+ puts 'When status = "Risk Accepted" POA&M Item CAN NOT be saved with the following parameters/fields:'.red
379
+ puts ' scheduledCompletionDate, or milestone'.red
380
+ puts POAMS_PUT_HELP_MESSAGE.yellow
381
+ exit
382
+ else
383
+ body.comments = options[:comments]
384
+ end
385
+ elsif options[:status] == "Ongoing"
386
+ if options[:scheduledCompletionDate].nil? || options[:milestone].nil?
387
+ puts 'When status = "Ongoing" the following parameters/fields are required:'.red
388
+ puts ' scheduledCompletionDate, milestone'.red
389
+ print_milestone_help
390
+ puts POAMS_PUT_HELP_MESSAGE.yellow
391
+ exit
392
+ elsif options[:milestone]["description"].nil? || options[:milestone]["scheduledCompletionDate"].nil?
393
+ puts 'Missing milstone parameters/fields'.red
394
+ print_milestone_help
395
+ exit
396
+ else
397
+ body.scheduled_completion_date = options[:scheduledCompletionDate]
398
+
399
+ milestone = EmassClient::MilestonesRequiredPut.new
400
+ milestone.milestone_id = options[:milestone]["milestoneId"] if options[:milestone]["milestoneId"]
401
+ milestone.description = options[:milestone]["description"]
402
+ milestone.scheduled_completion_date = options[:milestone]["scheduledCompletionDate"]
403
+ milestone_array = Array.new(1, milestone)
404
+ body.milestones = milestone_array
405
+ end
406
+ elsif options[:status] == "Completed"
407
+ if options[:scheduledCompletionDate].nil? || options[:comments].nil? ||
408
+ options[:completionDate].nil? || options[:milestone].nil?
409
+ puts 'Missing one of these parameters/fields:'.red
410
+ puts ' scheduledCompletionDate, comments, completionDate, or milestone'.red
411
+ print_milestone_help
412
+ puts POAMS_PUT_HELP_MESSAGE.yellow
413
+ exit
414
+ else
415
+ body.scheduled_completion_date = options[:scheduledCompletionDate]
416
+ body.comments = options[:comments]
417
+ body.completion_date = options[:completionDate]
418
+
419
+ milestone = EmassClient::MilestonesRequiredPut.new
420
+ milestone.milestone_id = options[:milestone]["milestoneId"] if options[:milestone]["milestoneId"]
421
+ milestone.description = options[:milestone]["description"]
422
+ milestone.scheduled_completion_date = options[:milestone]["scheduledCompletionDate"]
423
+ milestone_array = Array.new(1, milestone)
424
+ body.milestones = milestone_array
425
+ end
426
+ end
427
+
428
+ # POC checks: If any poc information is provided all POC fields are required
429
+ if options[:pocFirstName]
430
+ if options[:pocLastName].nil? || options[:pocEmail].nil? || options[:pocPhoneNumber].nil?
431
+ puts 'If a POC first name is given, then all POC information must be entered:'.red
432
+ puts ' pocLastName, pocEmail, pocPhoneNumber'.red
433
+ puts POAMS_PUT_HELP_MESSAGE.yellow
434
+ exit
435
+ end
436
+ elsif options[:pocLastName]
437
+ if options[:pocFirstName].nil? || options[:pocEmail].nil? || options[:pocPhoneNumber].nil?
438
+ puts 'If a POC last name is given, then all POC information must be entered:'.red
439
+ puts ' pocFirstName, pocEmail, pocPhoneNumber'.red
440
+ puts POAMS_PUT_HELP_MESSAGE.yellow
441
+ exit
442
+ end
443
+ elsif options[:pocEmail]
444
+ if options[:pocFirstName].nil? || options[:pocLastName].nil? || options[:pocPhoneNumber].nil?
445
+ puts 'If a POC email is given, then all POC information must be entered:'.red
446
+ puts ' pocFirstName, pocLastName, pocPhoneNumber'.red
447
+ puts POAMS_PUT_HELP_MESSAGE.yellow
448
+ exit
449
+ end
450
+ elsif options[:pocPhoneNumber]
451
+ if options[:pocFirstName].nil? || options[:pocLastName].nil? || options[:pocEmail].nil?
452
+ puts 'If a POC phone number is given, then all POC information must be entered:'.red
453
+ puts ' pocFirstName, pocLastName, pocEmail'.red
454
+ puts POAMS_PUT_HELP_MESSAGE.yellow
455
+ exit
456
+ end
457
+ end
458
+ # rubocop:enable Style/CaseLikeIf, Style/StringLiterals
459
+ end
460
+
461
+ def print_milestone_help
462
+ puts 'Milestone format is:'.yellow
463
+ puts ' --milestone milestoneId:[value] description:"[value]" scheduledCompletionDate:"[value]"'.yellow
464
+ puts 'The milestoneId:[value] is optional, if not provided a new milestone is created'.yellow
465
+ end
466
+ end
467
+ # rubocop:enable Metrics/AbcSize, Metrics/BlockLength, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
468
+ end
469
+
470
+ # Update Milestones items to a system.
471
+ #
472
+ # Endpoint:
473
+ # /api/systems/{systemId}/poams/{poamId}/milestones - Update milestones in one or many poa&m items in a system
474
+ class Milestones < SubCommandBase
475
+ def self.exit_on_failure?
476
+ true
477
+ end
478
+
479
+ desc 'update', 'Update milestone(s) for given specified system and poam'
480
+ long_desc Help.text(:milestone_put_mapper)
481
+
482
+ # Required parameters/fields
483
+ option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
484
+ option :poamId, type: :numeric, required: true, desc: 'A numeric value representing the poam identification'
485
+ option :milestoneId,
486
+ type: :numeric, required: true, desc: 'A numeric value representing the milestone identification'
487
+ option :description, type: :string, required: true, desc: 'The milestone description'
488
+ option :scheduledCompletionDate,
489
+ type: :numeric, required: false, desc: 'The scheduled completion date - Unix time format'
490
+
491
+ def update
492
+ body = EmassClient::MilestonesGet.new
493
+ body.milestone_id = options[:milestoneId]
494
+ body.description = options[:description]
495
+ body.scheduled_completion_date = options[:scheduledCompletionDate]
496
+ body_array = Array.new(1, body)
497
+
498
+ begin
499
+ # Get milestones in one or many poa&m items in a system
500
+ result = EmassClient::MilestonesApi
501
+ .new
502
+ .update_milestone_by_system_id_and_poam_id(options[:systemId], options[:poamId], body_array)
503
+ puts to_output_hash(result).green
504
+ rescue EmassClient::ApiError => e
505
+ puts 'Exception when calling MilestonesApi->update_milestone_by_system_id_and_poam_id'.red
506
+ puts to_output_hash(e)
507
+ end
508
+ end
509
+ end
510
+
511
+ # Update one or many artifacts for a system (this implementation only updates one artifact per each execution)
512
+ #
513
+ # Endpoint:
514
+ # /api/systems/{systemId}/artifacts - Put (update) one or many artifacts for a system
515
+ class Artifacts < SubCommandBase
516
+ def self.exit_on_failure?
517
+ true
518
+ end
519
+
520
+ desc 'update', 'Updates artifacts for a system with provided entries'
521
+ long_desc Help.text(:artifacts_put_mapper)
522
+
523
+ # Required parameters/fields
524
+ option :systemId, type: :numeric, required: true, desc: 'A numeric value representing the system identification'
525
+ option :filename, type: :string, required: true, desc: 'Artifact file name to be updated'
526
+ option :type,
527
+ type: :string, required: true,
528
+ enum: ['Procedure', 'Diagram', 'Policy', 'Labor', 'Document',
529
+ 'Image', 'Other', 'Scan Result', 'Auditor Report']
530
+ option :category, type: :string, required: true, enum: ['Implementation Guidance', 'Evidence']
531
+ option :isTemplate, type: :boolean, required: false, default: false, desc: 'BOOLEAN - true or false.'
532
+ # NOTE: compress is a required parameter, however Thor does not allow a boolean type to be required because it
533
+ # automatically creates a --no-isTemplate option for isTemplate=false
534
+
535
+ # Optional fields
536
+ option :description, type: :string, required: false, desc: 'Artifact description'
537
+ option :refPageNumber, type: :string, required: false, desc: 'Artifact reference page number'
538
+ option :ccis, type: :string, required: false, desc: 'The system CCIs string numerical value'
539
+ option :controls,
540
+ type: :string, required: false,
541
+ desc: 'Control acronym associated with the artifact. NIST SP 800-53 Revision 4 defined'
542
+ option :artifactExpirationDate,
543
+ type: :numeric, required: false, desc: 'Date Artifact expires and requires review - Unix time format'
544
+ option :lastReviewedDate,
545
+ type: :numeric, required: false, desc: 'Date Artifact was last reviewed - Unix time format'
546
+
547
+ # rubocop:disable Metrics/CyclomaticComplexity
548
+ def update
549
+ body = EmassClient::ArtifactsGet.new
550
+ body.filename = options[:filename]
551
+ body.type = options[:type]
552
+ body.category = options[:category]
553
+ body.is_template = options[:isTemplate]
554
+ # Optional fields
555
+ body.description = options[:description] if options[:description]
556
+ body.ref_page_number = options[:refPageNumber] if options[:refPageNumber]
557
+ body.ccis = options[:ccis] if options[:ccis]
558
+ body.controls = options[:controls] if options[:controls]
559
+ body.artifact_expiration_date = options[:artifactExpirationDate] if options[:artifactExpirationDate]
560
+ body.last_reviewed_date = options[:lastReviewedDate] if options[:lastReviewedDate]
561
+
562
+ body_array = Array.new(1, body)
563
+
564
+ begin
565
+ result = EmassClient::ArtifactsApi.new.update_artifact_by_system_id(options[:systemId], body_array)
566
+ puts to_output_hash(result).green
567
+ rescue EmassClient::ApiError => e
568
+ puts 'Exception when calling ArtifactsApi->update_artifact_by_system_id'.red
569
+ puts to_output_hash(e)
570
+ end
571
+ end
572
+ # rubocop:enable Metrics/CyclomaticComplexity
573
+ end
574
+
575
+ class Put < SubCommandBase
576
+ desc 'controls', 'Update system Controls'
577
+ subcommand 'controls', Controls
578
+
579
+ desc 'poams', 'Update Plan of Action (POA&M) items for a system'
580
+ subcommand 'poams', Poams
581
+
582
+ desc 'milestones', 'Update Milestones items for a system'
583
+ subcommand 'milestones', Milestones
584
+
585
+ desc 'artifacts', 'Put system Artifacts'
586
+ subcommand 'artifacts', Artifacts
587
+ end
588
+ end