emasser 3.4.1 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
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 +39 -72
  13. data/.gitignore +19 -19
  14. data/.mergify.yml +25 -25
  15. data/.rubocop.yml +83 -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 +108 -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 +1501 -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 +1194 -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 +115 -111
  57. data/lib/emasser/post.rb +830 -830
  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 +16 -10
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