nexpose_ticketing 0.0.1 → 0.2.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.
@@ -0,0 +1,18 @@
1
+ ---
2
+ # This configuration file defines all the options necessary to support the helper.
3
+ # Fields marked (M) are mandatory.
4
+ #
5
+
6
+ # (M) Helper class name
7
+ :helper_name: ServiceNowHelper
8
+
9
+ # (M) ServiceNow url (currently requires using JSON)
10
+ :servicenow_url: https://myinstance.service-now.com/incident.do?JSON
11
+ # (M) Username for ServiceNow
12
+ :username: admin
13
+ # (M) Password for above username
14
+ :password: password
15
+ # (M) If 'Y', SSL connections will output to stderr (default is 'N')
16
+ :verbose_mode: N
17
+ # (M) Amount of times the helper will follow 301/302 redirections
18
+ :redirect_limit: 10
@@ -22,6 +22,6 @@
22
22
  # (M) Nexpose console hostname.
23
23
  :nxconsole: 127.0.0.1
24
24
  # (M) Nexpose username.
25
- :nxuser: nxusername
25
+ :nxuser: nxadmin
26
26
  # (M) Nexpose password.
27
- :nxpasswd: nxpassword
27
+ :nxpasswd: nxadmin
@@ -1,12 +1,12 @@
1
- # This class serves as the JIRA interface
2
- # that creates issues within JIRA from vulnerabilities
3
- # found in Nexpose.
4
- # Copyright:: Copyright (c) 2014 Rapid7, LLC.
5
1
  require 'json'
6
2
  require 'net/http'
7
3
  require 'net/https'
8
4
  require 'uri'
9
5
  require 'csv'
6
+ # This class serves as the JIRA interface
7
+ # that creates issues within JIRA from vulnerabilities
8
+ # found in Nexpose.
9
+ # Copyright:: Copyright (c) 2014 Rapid7, LLC.
10
10
  class JiraHelper
11
11
  attr_accessor :jira_data, :options
12
12
  def initialize(jira_data, options)
@@ -14,7 +14,7 @@ class JiraHelper
14
14
  @options = options
15
15
  end
16
16
 
17
- def create_ticket(tickets)
17
+ def create_tickets(tickets)
18
18
  fail 'Ticket(s) cannot be empty.' if tickets.empty? || tickets.nil?
19
19
  tickets.each do |ticket|
20
20
  headers = { 'Content-Type' => 'application/json',
@@ -33,7 +33,7 @@ class JiraHelper
33
33
  end
34
34
 
35
35
  # Prepares tickets from the CSV.
36
- def prepare_tickets(vulnerability_list)
36
+ def prepare_create_tickets(vulnerability_list)
37
37
  @ticket = Hash.new(-1)
38
38
  case @options[:ticket_mode]
39
39
  # 'D' Default IP *-* Vulnerability
@@ -0,0 +1,448 @@
1
+ require 'json'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'csv'
6
+ require 'savon'
7
+ require 'nexpose_ticketing/nx_logger'
8
+
9
+ # Serves as the Remedy interface for creating/updating issues from
10
+ # vulnelrabilities found in Nexpose.
11
+ class RemedyHelper
12
+ attr_accessor :remedy_data, :options, :log, :client
13
+ def initialize(remedy_data, options)
14
+ @remedy_data = remedy_data
15
+ @options = options
16
+ @log = NexposeTicketing::NXLogger.new
17
+ end
18
+
19
+ # Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
20
+ # as a separate web service call).
21
+ #
22
+ # * *Args* :
23
+ # - +tickets+ - List of savon-formatted (hash) ticket creates (new tickets).
24
+ #
25
+ def create_tickets(tickets)
26
+ fail 'Ticket(s) cannot be empty' if tickets.nil? || tickets.empty?
27
+ client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_Create_WS.xml'),
28
+ ssl_verify_mode: :none,
29
+ open_timeout: @remedy_data[:open_timeout],
30
+ read_timeout: @remedy_data[:read_timeout],
31
+ endpoint: @remedy_data[:create_soap_endpoint],
32
+ soap_header: { 'AuthenticationInfo' =>
33
+ { 'userName' => "#{@remedy_data[:username]}",
34
+ 'password' => "#{@remedy_data[:password]}",
35
+ 'authentication' => "#{@remedy_data[:authentication]}"
36
+ }
37
+ })
38
+ tickets.each do |ticket|
39
+ begin
40
+ @log.log_message(ticket)
41
+ response = client.call(:help_desk_submit_service, message: ticket)
42
+ rescue Savon::SOAPFault => e
43
+ @log.log_message("SOAP exception in create ticket: #{e.message}")
44
+ raise
45
+ rescue Savon::HTTPError => e
46
+ @log.log_message("HTTP error in create ticket: #{e.message}")
47
+ raise
48
+ end
49
+ end
50
+ end
51
+
52
+ # Sends ticket updates (in SOAP format) to Remedy individually (each ticket in the list
53
+ # as a separate web service call).
54
+ #
55
+ # * *Args* :
56
+ # - +tickets+ - List of savon-formatted (hash) ticket updates.
57
+ #
58
+ def update_tickets(tickets)
59
+ if tickets.nil? || tickets.empty?
60
+ @log.log_message("No tickets to update.")
61
+ else
62
+ client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_WS.xml'),
63
+ ssl_verify_mode: :none,
64
+ open_timeout: @remedy_data[:open_timeout],
65
+ read_timeout: @remedy_data[:read_timeout],
66
+ endpoint: @remedy_data[:query_modify_soap_endpoint],
67
+ soap_header: { 'AuthenticationInfo' =>
68
+ { 'userName' => "#{@remedy_data[:username]}",
69
+ 'password' => "#{@remedy_data[:password]}",
70
+ 'authentication' => "#{@remedy_data[:authentication]}"
71
+ }
72
+ })
73
+ tickets.each do |ticket|
74
+ begin
75
+ response = client.call(:help_desk_modify_service, message: ticket)
76
+ rescue Savon::SOAPFault => e
77
+ @log.log_message("SOAP exception in create ticket: #{e.message}")
78
+ raise
79
+ rescue Savon::HTTPError => e
80
+ @log.log_message("HTTP error in create ticket: #{e.message}")
81
+ raise
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # Sends ticket closure (in SOAP format) to Remedy individually (each ticket in the list
88
+ # as a separate web service call).
89
+ #
90
+ # * *Args* :
91
+ # - +tickets+ - List of savon-formatted (hash) ticket closures.
92
+ #
93
+ def close_tickets(tickets)
94
+ if tickets.nil? || tickets.empty?
95
+ @log.log_message("No tickets to close.")
96
+ else
97
+ client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_WS.xml'),
98
+ ssl_verify_mode: :none,
99
+ open_timeout: @remedy_data[:open_timeout],
100
+ read_timeout: @remedy_data[:read_timeout],
101
+ endpoint: @remedy_data[:query_modify_soap_endpoint],
102
+ soap_header: { 'AuthenticationInfo' =>
103
+ { 'userName' => "#{@remedy_data[:username]}",
104
+ 'password' => "#{@remedy_data[:password]}",
105
+ 'authentication' => "#{@remedy_data[:authentication]}"
106
+ }
107
+ })
108
+ tickets.each do |ticket|
109
+ begin
110
+ response = client.call(:help_desk_modify_service, message: ticket)
111
+ rescue Savon::SOAPFault => e
112
+ @log.log_message("SOAP exception in create ticket: #{e.message}")
113
+ raise
114
+ rescue Savon::HTTPError => e
115
+ @log.log_message("HTTP error in create ticket: #{e.message}")
116
+ raise
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ # Sends a query (in SOAP format) to Remedy to return back a single ticket based on the criteria.
123
+ #
124
+ # * *Args* :
125
+ # - +unique_id+ - Unique identifier generated by the helper.
126
+ #
127
+ # * *Returns* :
128
+ # - Remedy incident information in hash format or nil if no results are found.
129
+ #
130
+ def query_for_ticket(unique_id)
131
+ client = Savon.client(wsdl: File.join(File.dirname(__FILE__), '../config/remedy_wsdl/HPD_IncidentInterface_WS.xml'),
132
+ ssl_verify_mode: :none,
133
+ open_timeout: @remedy_data[:open_timeout],
134
+ read_timeout: @remedy_data[:read_timeout],
135
+ endpoint: @remedy_data[:query_modify_soap_endpoint],
136
+ soap_header: { 'AuthenticationInfo' =>
137
+ { 'userName' => "#{@remedy_data[:username]}",
138
+ 'password' => "#{@remedy_data[:password]}",
139
+ 'authentication' => "#{@remedy_data[:authentication]}"
140
+ }
141
+ })
142
+ begin
143
+ response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Detailed Decription' LIKE \"%#{unique_id}%\""})
144
+ rescue Savon::SOAPFault => e
145
+ @log.log_message("SOAP exception in query ticket: #{e.message}")
146
+ return if e.to_hash[:fault][:faultstring].index("ERROR (302)") == 0
147
+ raise
148
+ rescue Savon::HTTPError => e
149
+ @log.log_message("HTTP error in query ticket: #{e.message}")
150
+ raise
151
+ end
152
+
153
+ response.body[:help_desk_query_list_service_response][:get_list_values]
154
+ end
155
+
156
+ # Prepare tickets from the CSV of vulnerabilities exported from Nexpose. This method determines
157
+ # how to prepare the tickets (either by default or by IP address) based on config options.
158
+ #
159
+ # * *Args* :
160
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
161
+ #
162
+ # * *Returns* :
163
+ # - List of savon-formated (hash) tickets for creating within Remedy.
164
+ #
165
+ def prepare_create_tickets(vulnerability_list)
166
+ @ticket = Hash.new(-1)
167
+ case @options[:ticket_mode]
168
+ # 'D' Default mode: IP *-* Vulnerability
169
+ when 'D'
170
+ prepare_create_tickets_default(vulnerability_list)
171
+ # 'I' IP address mode: IP address -* Vulnerability
172
+ when 'I'
173
+ prepare_create_tickets_by_ip(vulnerability_list)
174
+ else
175
+ fail 'No ticketing mode selected.'
176
+ end
177
+ end
178
+
179
+ # Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
180
+ # Remedy. The preparation by default means that each vulnerability within Nexpose is a
181
+ # separate incident within Remedy. This makes for smaller, more actionalble incidents but
182
+ # could lead to a very large total number of incidents.
183
+ #
184
+ # * *Args* :
185
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
186
+ #
187
+ # * *Returns* :
188
+ # - List of savon-formated (hash) tickets for creating within Remedy.
189
+ #
190
+ def prepare_create_tickets_default(vulnerability_list)
191
+ @log.log_message("Preparing tickets by default method.")
192
+ tickets = []
193
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
194
+ # NXID in the notes is a unique identifier used to query incidents to update/resolve
195
+ # incidents as they are resolved in Nexpose.
196
+ ticket = {
197
+ 'First_Name' => "#{@remedy_data[:first_name]}",
198
+ 'Impact' => '1-Extensive/Widespread',
199
+ 'Last_Name' => "#{@remedy_data[:last_name]}",
200
+ 'Reported_Source' => 'Other',
201
+ 'Service_Type' => 'Infrastructure Event',
202
+ 'Status' => 'New',
203
+ 'Action' => 'CREATE',
204
+ 'Summary' => "#{row['ip_address']} => #{row['summary']}",
205
+ 'Notes' => "Summary: #{row['summary']} \n\nFix: #{row['fix']} \n\nURL: #{row['url']}
206
+ \n\nNXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
207
+ 'Urgency' => '1-Critical'
208
+ }
209
+ tickets.push(ticket)
210
+ end
211
+ tickets
212
+ end
213
+
214
+ # Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
215
+ # Remedy. The preparation by IP means that all vulnerabilities within Nexpose for one IP
216
+ # address are consolidated into a single Remedy incident. This reduces the number of incidents
217
+ # within ServiceNow but greatly increases the size of the work notes.
218
+ #
219
+ # * *Args* :
220
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
221
+ #
222
+ # * *Returns* :
223
+ # - List of savon-formated (hash) tickets for creating within Remedy.
224
+ #
225
+ def prepare_create_tickets_by_ip(vulnerability_list)
226
+ @log.log_message("Preparing tickets by IP address.")
227
+ tickets = []
228
+ current_ip = -1
229
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
230
+ if current_ip == -1
231
+ current_ip = row['ip_address']
232
+ @log.log_message("Creating ticket with IP address: #{row['ip_address']}")
233
+ @ticket = {
234
+ 'First_Name' => "#{@remedy_data[:first_name]}",
235
+ 'Impact' => '1-Extensive/Widespread',
236
+ 'Last_Name' => "#{@remedy_data[:last_name]}",
237
+ 'Reported_Source' => 'Other',
238
+ 'Service_Type' => 'Infrastructure Event',
239
+ 'Status' => 'New',
240
+ 'Action' => 'CREATE',
241
+ 'Summary' => "#{row['ip_address']} => Vulnerabilities",
242
+ 'Notes' => "++ New Vulnerabilities +++++++++++++++++++++++++++++++++++++\n",
243
+ 'Urgency' => '1-Critical'
244
+ }
245
+ end
246
+ if current_ip == row['ip_address']
247
+ @ticket['Notes'] +=
248
+ "\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
249
+ unless row['url'].nil?
250
+ @ticket['Notes'] +=
251
+ "\nURL: #{row['url']}"
252
+ end
253
+ end
254
+ unless current_ip == row['ip_address']
255
+ # NXID in the work_notes is the unique identifier used to query incidents to update them.
256
+ @ticket['Notes'] += "\n\nNXID: #{current_ip}"
257
+ tickets.push(@ticket)
258
+ current_ip = -1
259
+ redo
260
+ end
261
+ end
262
+ # NXID in the work_notes is the unique identifier used to query incidents to update them.
263
+ @ticket['Notes'] += "\n\nNXID: #{current_ip}"
264
+ tickets.push(@ticket) unless @ticket.nil?
265
+ tickets
266
+ end
267
+
268
+ # Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
269
+ # currently only supports updating IP-address mode tickets in Remedy. The list of vulnerabilities
270
+ # are ordered by IP address and then by ticket_status, allowing the method to loop through and
271
+ # display new, old, and same vulnerabilities in that order.
272
+ #
273
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
274
+ #
275
+ # * *Returns* :
276
+ # - List of savon-formated (hash) tickets for updating within Remedy.
277
+ #
278
+ def prepare_update_tickets(vulnerability_list)
279
+ fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] == 'D'
280
+ @ticket = Hash.new(-1)
281
+
282
+ @log.log_message("Preparing ticket updates by IP address.")
283
+ tickets = []
284
+ current_ip = -1
285
+ ticket_status = 'New'
286
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
287
+ if current_ip == -1
288
+ current_ip = row['ip_address']
289
+ ticket_status = row['comparison']
290
+
291
+ # Query Remedy for the incident by unique id (generated NXID)
292
+ queried_incident = query_for_ticket("NXID: #{row['ip_address']}")
293
+ if queried_incident.nil? || queried_incident.empty?
294
+ @log.log_message("No incident found for NXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}")
295
+ else
296
+ @log.log_message("Creating ticket update with IP address: #{row['ip_address']}")
297
+ @log.log_message("Ticket status #{ticket_status}")
298
+ # Remedy incident updates require populating all fields.
299
+ @ticket = {
300
+ 'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
301
+ 'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
302
+ 'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
303
+ 'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
304
+ 'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
305
+ 'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
306
+ 'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
307
+ 'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
308
+ 'Closure_Product_Name' => queried_incident[:closure_product_name],
309
+ 'Company' => queried_incident[:company],
310
+ 'Summary' => queried_incident[:summary],
311
+ 'Notes' => "++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++\n",
312
+ 'Impact' => queried_incident[:impact],
313
+ 'Manufacturer' => queried_incident[:manufacturer],
314
+ 'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
315
+ 'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
316
+ 'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
317
+ 'Product_Model_Version' => queried_incident[:product_model_version],
318
+ 'Product_Name' => queried_incident[:product_name],
319
+ 'Reported_Source' => queried_incident[:reported_source],
320
+ 'Resolution' => queried_incident[:resolution],
321
+ 'Resolution_Category' => queried_incident[:resolution_category],
322
+ 'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
323
+ 'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
324
+ 'Resolution_Method' => queried_incident[:resolution_method],
325
+ 'Service_Type' => queried_incident[:service_type],
326
+ 'Status' => queried_incident[:status],
327
+ 'Urgency' => queried_incident[:urgency],
328
+ 'Action' => 'MODIFY',
329
+ 'Work_Info_Summary' => queried_incident[:work_info_summary],
330
+ 'Work_Info_Notes' => queried_incident[:work_info_notes],
331
+ 'Work_Info_Type' => queried_incident[:work_info_type],
332
+ 'Work_Info_Date' => queried_incident[:work_info_date],
333
+ 'Work_Info_Source' => queried_incident[:work_info_source],
334
+ 'Work_Info_Locked' => queried_incident[:work_info_locked],
335
+ 'Work_Info_View_Access' => queried_incident[:work_info_view_access],
336
+ 'Incident_Number' => queried_incident[:incident_number],
337
+ 'Status_Reason' => queried_incident[:status_reason],
338
+ 'ServiceCI' => queried_incident[:service_ci],
339
+ 'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
340
+ 'HPD_CI' => queried_incident[:hpd_ci],
341
+ 'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
342
+ 'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
343
+ 'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
344
+ }
345
+ end
346
+ end
347
+ if current_ip == row['ip_address']
348
+ # If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
349
+ unless ticket_status == row['comparison']
350
+ @ticket['Notes'] +=
351
+ "\n\n\n++ #{row['comparison']} Vulnerabilities +++++++++++++++++++++++++++++++++++++\n"
352
+ ticket_status = row['comparison']
353
+ end
354
+
355
+ @ticket['Notes'] +=
356
+ "\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
357
+ # Only add the URL block if data exists in the row.
358
+ unless row['url'].nil?
359
+ @ticket['Notes'] +=
360
+ "\nURL: #{row['url']}"
361
+ end
362
+ end
363
+ unless current_ip == row['ip_address']
364
+ # NXID in the work_notes is the unique identifier used to query incidents to update them.
365
+ @ticket['Notes'] += "\n\nNXID: #{current_ip}"
366
+ tickets.push(@ticket)
367
+ current_ip = -1
368
+ redo
369
+ end
370
+ end
371
+ # NXID in the work_notes is the unique identifier used to query incidents to update them.
372
+ @ticket['Notes'] += "\n\nNXID: #{current_ip}"
373
+ tickets.push(@ticket) unless @ticket.nil?
374
+ tickets
375
+ end
376
+
377
+ # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose. This method
378
+ # currently only supports updating default mode tickets in ServiceNow.
379
+ #
380
+ # * *Args* :
381
+ # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
382
+ #
383
+ # * *Returns* :
384
+ # - List of savon-formated (hash) tickets for closing within Remedy.
385
+ #
386
+ def prepare_close_tickets(vulnerability_list)
387
+ fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
388
+ @log.log_message("Preparing ticket closures by default method.")
389
+ tickets = []
390
+ CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
391
+ # Query Remedy for the incident by unique id (generated NXID)
392
+ queried_incident = query_for_ticket("NXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}")
393
+ if queried_incident.nil? || queried_incident.empty?
394
+ @log.log_message("No incident found for NXID: #{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}")
395
+ else
396
+ # Remedy incident updates require populating all fields.
397
+ ticket = {
398
+ 'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
399
+ 'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
400
+ 'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
401
+ 'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
402
+ 'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
403
+ 'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
404
+ 'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
405
+ 'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
406
+ 'Closure_Product_Name' => queried_incident[:closure_product_name],
407
+ 'Company' => queried_incident[:company],
408
+ 'Summary' => queried_incident[:summary],
409
+ 'Notes' => queried_incident[:notes],
410
+ 'Impact' => queried_incident[:impact],
411
+ 'Manufacturer' => queried_incident[:manufacturer],
412
+ 'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
413
+ 'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
414
+ 'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
415
+ 'Product_Model_Version' => queried_incident[:product_model_version],
416
+ 'Product_Name' => queried_incident[:product_name],
417
+ 'Reported_Source' => queried_incident[:reported_source],
418
+ 'Resolution' => queried_incident[:resolution],
419
+ 'Resolution_Category' => queried_incident[:resolution_category],
420
+ 'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
421
+ 'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
422
+ 'Resolution_Method' => queried_incident[:resolution_method],
423
+ 'Service_Type' => queried_incident[:service_type],
424
+ 'Status' => 'Closed',
425
+ 'Urgency' => queried_incident[:urgency],
426
+ 'Action' => 'MODIFY',
427
+ 'Work_Info_Summary' => queried_incident[:work_info_summary],
428
+ 'Work_Info_Notes' => queried_incident[:work_info_notes],
429
+ 'Work_Info_Type' => queried_incident[:work_info_type],
430
+ 'Work_Info_Date' => queried_incident[:work_info_date],
431
+ 'Work_Info_Source' => queried_incident[:work_info_source],
432
+ 'Work_Info_Locked' => queried_incident[:work_info_locked],
433
+ 'Work_Info_View_Access' => queried_incident[:work_info_view_access],
434
+ 'Incident_Number' => queried_incident[:incident_number],
435
+ 'Status_Reason' => queried_incident[:status_reason],
436
+ 'ServiceCI' => queried_incident[:service_ci],
437
+ 'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
438
+ 'HPD_CI' => queried_incident[:hpd_ci],
439
+ 'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
440
+ 'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
441
+ 'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
442
+ }
443
+ tickets.push(ticket)
444
+ end
445
+ end
446
+ tickets
447
+ end
448
+ end