nexpose_ticketing 0.0.1 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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