nexpose_ticketing 0.8.3 → 1.0.0

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.
@@ -5,6 +5,8 @@ require 'uri'
5
5
  require 'csv'
6
6
  require 'savon'
7
7
  require 'nexpose_ticketing/nx_logger'
8
+ require 'nexpose_ticketing/version'
9
+ require 'nexpose_ticketing/common_helper'
8
10
 
9
11
  # Serves as the Remedy interface for creating/updating issues from
10
12
  # vulnelrabilities found in Nexpose.
@@ -13,42 +15,90 @@ class RemedyHelper
13
15
  def initialize(remedy_data, options)
14
16
  @remedy_data = remedy_data
15
17
  @options = options
16
- @log = NexposeTicketing::NXLogger.new
18
+ @log = NexposeTicketing::NxLogger.instance
19
+ @common_helper = NexposeTicketing::CommonHelper.new(@options)
20
+ end
21
+
22
+ # Generates a savon-based ticket object.
23
+ #
24
+ # * *Args* :
25
+ # - +extra_fields+ - List of mode-specific fields (hash) to be added to the ticket.
26
+ #
27
+ def generate_new_ticket(extra_fields=nil)
28
+ base_ticket = {
29
+ 'First_Name' => "#{@remedy_data[:first_name]}",
30
+ 'Impact' => '1-Extensive/Widespread',
31
+ 'Last_Name' => "#{@remedy_data[:last_name]}",
32
+ 'Reported_Source' => 'Other',
33
+ 'Service_Type' => 'Infrastructure Event',
34
+ 'Status' => 'New',
35
+ 'Action' => 'CREATE',
36
+ "Summary"=>"",
37
+ "Notes"=>"",
38
+ 'Urgency' => '1-Critical',
39
+ }
40
+ extra_fields.each { |k, v| base_ticket[k.to_s] = v } if extra_fields
41
+ base_ticket
42
+ end
43
+
44
+ # Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
45
+ # as a separate web service call).
46
+ #
47
+ # * *Args* :
48
+ # - +wdsl+ - XML file which describes the network service.
49
+ # - +endpoint+ - Endpoint to which the data will be submitted.
50
+ #
51
+ def get_client(wdsl, endpoint)
52
+ Savon.client(wsdl: File.join(File.dirname(__FILE__), "../config/remedy_wsdl/#{wdsl}"),
53
+ adapter: :net_http,
54
+ ssl_verify_mode: :none,
55
+ open_timeout: @remedy_data[:open_timeout],
56
+ read_timeout: @remedy_data[:read_timeout],
57
+ endpoint: @remedy_data[endpoint.intern],
58
+ soap_header: { 'AuthenticationInfo' =>
59
+ { 'userName' => "#{@remedy_data[:username]}",
60
+ 'password' => "#{@remedy_data[:password]}",
61
+ 'authentication' => "#{@remedy_data[:authentication]}"
62
+ }
63
+ })
17
64
  end
18
65
 
19
66
  # Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
20
67
  # as a separate web service call).
21
68
  #
22
69
  # * *Args* :
70
+ # - +service+ - The helpdesk service to which the tickets should be submitted.
23
71
  # - +tickets+ - List of savon-formatted (hash) ticket creates (new tickets).
24
72
  #
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
- })
73
+ def send_tickets(client, service, tickets)
74
+ service_name = service.to_s.match(/desk_([a-z]*)_/)
75
+ service_name = service_name.captures.first unless service_name.nil?
38
76
  tickets.each do |ticket|
39
77
  begin
40
78
  @log.log_message(ticket)
41
- response = client.call(:help_desk_submit_service, message: ticket)
79
+ response = client.call(service, message: ticket)
42
80
  rescue Savon::SOAPFault => e
43
- @log.log_message("SOAP exception in create ticket: #{e.message}")
81
+ @log.log_message("SOAP exception in #{service_name} ticket: #{e.message}")
44
82
  raise
45
83
  rescue Savon::HTTPError => e
46
- @log.log_message("HTTP error in create ticket: #{e.message}")
84
+ @log.log_message("HTTP error in #{service_name} ticket: #{e.message}")
47
85
  raise
48
86
  end
49
87
  end
50
88
  end
51
-
89
+
90
+ # Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list
91
+ # as a separate web service call).
92
+ #
93
+ # * *Args* :
94
+ # - +tickets+ - List of savon-formatted (hash) ticket creates (new tickets).
95
+ #
96
+ def create_tickets(tickets)
97
+ fail 'Ticket(s) cannot be empty' if tickets.nil? || tickets.empty?
98
+ client = get_client('HPD_IncidentInterface_Create_WS.xml', :create_soap_endpoint)
99
+ send_tickets(client, :help_desk_submit_service, tickets)
100
+ end
101
+
52
102
  # Sends ticket updates (in SOAP format) to Remedy individually (each ticket in the list
53
103
  # as a separate web service call).
54
104
  #
@@ -58,30 +108,10 @@ class RemedyHelper
58
108
  def update_tickets(tickets)
59
109
  if tickets.nil? || tickets.empty?
60
110
  @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
111
+ return
84
112
  end
113
+ client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
114
+ send_tickets(client, :help_desk_modify_service, tickets)
85
115
  end
86
116
 
87
117
  # Sends ticket closure (in SOAP format) to Remedy individually (each ticket in the list
@@ -93,30 +123,10 @@ class RemedyHelper
93
123
  def close_tickets(tickets)
94
124
  if tickets.nil? || tickets.empty?
95
125
  @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
126
+ return
119
127
  end
128
+ client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
129
+ send_tickets(client, :help_desk_modify_service, tickets)
120
130
  end
121
131
 
122
132
  # Sends a query (in SOAP format) to Remedy to return back a single ticket based on the criteria.
@@ -128,19 +138,10 @@ class RemedyHelper
128
138
  # - Remedy incident information in hash format or nil if no results are found.
129
139
  #
130
140
  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
- })
141
+ client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
142
+
142
143
  begin
143
- response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Detailed Decription' LIKE \"%#{unique_id}%\""})
144
+ response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Status' < \"Closed\" AND 'Detailed Decription' LIKE \"%#{unique_id}%\""})
144
145
  rescue Savon::SOAPFault => e
145
146
  @log.log_message("SOAP exception in query ticket: #{e.message}")
146
147
  return if e.to_hash[:fault][:faultstring].index("ERROR (302)") == 0
@@ -163,20 +164,19 @@ class RemedyHelper
163
164
  # - List of savon-formated (hash) tickets for creating within Remedy.
164
165
  #
165
166
  def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
166
- @ticket = Hash.new(-1)
167
+ @log.log_message('Preparing ticket requests...')
167
168
  case @options[:ticket_mode]
168
- # 'D' Default mode: IP *-* Vulnerability
169
- when 'D'
170
- prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
171
- # 'I' IP address mode: IP address -* Vulnerability
172
- when 'I'
173
- prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
174
- # 'V' Vulnerability mode: Vulnerability -* IP address
175
- when 'V'
176
- prepare_create_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
169
+ # 'D' Default IP *-* Vulnerability
170
+ when 'D' then matching_fields = ['ip_address', 'vulnerability_id']
171
+ # 'I' IP address -* Vulnerability
172
+ when 'I' then matching_fields = ['ip_address']
173
+ # 'V' Vulnerability -* Assets
174
+ when 'V' then matching_fields = ['vulnerability_id']
177
175
  else
178
- fail 'No ticketing mode selected.'
176
+ fail 'Unsupported ticketing mode selected.'
179
177
  end
178
+
179
+ prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
180
180
  end
181
181
 
182
182
  # Prepare to update tickets from the CSV of vulnerabilities exported from Nexpose. This method determines
@@ -189,113 +189,22 @@ class RemedyHelper
189
189
  # - List of savon-formated (hash) tickets for creating within Remedy.
190
190
  #
191
191
  def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
192
- @ticket = Hash.new(-1)
193
192
  case @options[:ticket_mode]
194
- # 'I' IP address mode: IP address -* Vulnerability
195
- when 'I'
196
- prepare_update_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
197
- # 'V' Vulnerability mode: Vulnerability -* IP address
198
- when 'V'
199
- prepare_update_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
200
- else
201
- fail 'No ticketing mode selected.'
202
- end
203
- end
204
-
205
- # Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
206
- # Remedy. The preparation by default means that each vulnerability within Nexpose is a
207
- # separate incident within Remedy. This makes for smaller, more actionalble incidents but
208
- # could lead to a very large total number of incidents.
209
- #
210
- # * *Args* :
211
- # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
212
- #
213
- # * *Returns* :
214
- # - List of savon-formated (hash) tickets for creating within Remedy.
215
- #
216
- def prepare_create_tickets_default(vulnerability_list, nexpose_identifier_id)
217
- @log.log_message("Preparing tickets by default method.")
218
- tickets = []
219
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
220
- # NXID in the notes is a unique identifier used to query incidents to update/resolve
221
- # incidents as they are resolved in Nexpose.
222
- ticket = {
223
- 'First_Name' => "#{@remedy_data[:first_name]}",
224
- 'Impact' => '1-Extensive/Widespread',
225
- 'Last_Name' => "#{@remedy_data[:last_name]}",
226
- 'Reported_Source' => 'Other',
227
- 'Service_Type' => 'Infrastructure Event',
228
- 'Status' => 'New',
229
- 'Action' => 'CREATE',
230
- 'Summary' => "#{row['ip_address']} => #{row['summary']}",
231
- 'Notes' => "Summary: #{row['summary']} \n\nFix: #{row['fix']} \n\nURL: #{row['url']}
232
- \n\nNXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}",
233
- 'Urgency' => '1-Critical'
234
- }
235
- tickets.push(ticket)
193
+ # 'D' Default IP *-* Vulnerability
194
+ when 'D' then fail 'Ticket updates are not supported in Default mode.'
195
+ # 'I' IP address -* Vulnerability
196
+ when 'I' then matching_fields = ['ip_address']
197
+ # 'V' Vulnerability -* Assets
198
+ when 'V' then matching_fields = ['vulnerability_id']
199
+ else
200
+ fail 'Unsupported ticketing mode selected.'
236
201
  end
237
- tickets
202
+
203
+ prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
238
204
  end
239
205
 
240
206
  # Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
241
- # Remedy. The preparation by IP means that all vulnerabilities within Nexpose for one IP
242
- # address are consolidated into a single Remedy incident. This reduces the number of incidents
243
- # within ServiceNow but greatly increases the size of the work notes.
244
- #
245
- # * *Args* :
246
- # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
247
- #
248
- # * *Returns* :
249
- # - List of savon-formated (hash) tickets for creating within Remedy.
250
- #
251
- def prepare_create_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
252
- @log.log_message('Preparing tickets by IP address.')
253
- tickets = []
254
- current_ip = -1
255
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
256
- if current_ip == -1
257
- current_ip = row['ip_address']
258
- @log.log_message("Creating ticket with IP address: #{row['ip_address']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}")
259
- @ticket = {
260
- 'First_Name' => "#{@remedy_data[:first_name]}",
261
- 'Impact' => '1-Extensive/Widespread',
262
- 'Last_Name' => "#{@remedy_data[:last_name]}",
263
- 'Reported_Source' => 'Other',
264
- 'Service_Type' => 'Infrastructure Event',
265
- 'Status' => 'New',
266
- 'Action' => 'CREATE',
267
- 'Summary' => "#{row['ip_address']} => Vulnerabilities",
268
- 'Notes' => "++ New Vulnerabilities +++++++++++++++++++++++++++++++++++++\n",
269
- 'Urgency' => '1-Critical'
270
- }
271
- end
272
- if current_ip == row['ip_address']
273
- @ticket['Notes'] +=
274
- "\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
275
- unless row['url'].nil?
276
- @ticket['Notes'] +=
277
- "\nURL: #{row['url']}"
278
- end
279
- end
280
- unless current_ip == row['ip_address']
281
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
282
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
283
- tickets.push(@ticket)
284
- current_ip = -1
285
- redo
286
- end
287
- end
288
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
289
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
290
- tickets.push(@ticket) unless @ticket.nil?
291
- tickets
292
- end
293
-
294
-
295
- # Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for
296
- # Remedy. The preparation by vulnerability means that all IP addresses within Nexpose for one vulnerability
297
- # are consolidated into a single Remedy incident. This reduces the number of incidents
298
- # within ServiceNow but greatly increases the size of the work notes.
207
+ # Remedy.
299
208
  #
300
209
  # * *Args* :
301
210
  # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
@@ -303,266 +212,131 @@ class RemedyHelper
303
212
  # * *Returns* :
304
213
  # - List of savon-formated (hash) tickets for creating within Remedy.
305
214
  #
306
- def prepare_create_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
307
- @log.log_message("Preparing tickets by vulnerability.")
308
- tickets = []
309
- current_vuln_id = -1
310
- current_solution_id = -1
311
- current_asset_id = -1
312
- full_summary = nil
313
- vulnerability_list_header = vulnerability_list[0]
314
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
315
- #New vulnerability ID (the query sorting criteria).
316
- if current_vuln_id == -1
317
- current_vuln_id = row['vulnerability_id']
318
- current_solution_id = row['solution_id']
319
- current_asset_id = row['asset_id']
320
- @log.log_message("Creating ticket with vulnerability id: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}")
321
- summary = "Vulnerability: #{row['title']}"
322
-
323
- #Remedy has a summary field max size of 100 so truncate any summaries that are larger than that and place the full summary in the notes.
324
- if summary.length > 100
325
- full_summary = summary += "\n\n\n"
326
- summary = summary[0..96]
327
- summary += '...'
328
- else
329
- full_summary = nil
330
- end
331
-
332
- @ticket = {
333
- 'First_Name' => "#{@remedy_data[:first_name]}",
334
- 'Impact' => '1-Extensive/Widespread',
335
- 'Last_Name' => "#{@remedy_data[:last_name]}",
336
- 'Reported_Source' => 'Other',
337
- 'Service_Type' => 'Infrastructure Event',
338
- 'Status' => 'New',
339
- 'Action' => 'CREATE',
340
- 'Summary' => summary,
341
- 'Full_Summary' => summary,
342
- 'Assets' => "++ Assets affected +++++++++++++++++++++++\n",
343
- 'Solutions' => "++ Details ++++++++++++++++++++++++++\n",
344
- 'Notes' => "++ Additional information ++++++++++++++++\n",
345
- 'Urgency' => '1-Critical'
346
- }
347
- @ticket['Solutions'] +=
348
- "\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
349
- @ticket['Assets'] +=
350
- "#{row['ip_address']}"
351
- end
352
- if current_vuln_id == row['vulnerability_id']
353
- #Add solutions for the now affected assets.
354
- if current_solution_id != row['solution_id']
355
- new_solution_text = "\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}\n"
356
- if @ticket['Solutions'].include? new_solution_text
357
- @log.log_message('Ignoring duplicate solution in ticket creation.')
358
- else
359
- @ticket['Solutions'] += new_solution_text
360
- #Add any references.
361
- unless row['url'].nil?
362
- @ticket['Solutions'] += "\nURL: #{row['url']}"
363
- end
364
- end
365
- current_solution_id = row['solution_id']
366
- end
367
-
368
- #Added the new asset to the list of affected systems if it is different (could have been the same asset with a different solution ID).
369
- if current_asset_id != row['asset_id']
370
- @ticket['Assets'] += ", #{row['ip_address']}"
371
- current_asset_id = row['asset_id']
372
- end
373
- end
374
- unless current_vuln_id == row['vulnerability_id']
375
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
376
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
377
- current_vuln_id = -1
378
- current_solution_id = -1
379
- current_asset_id = -1
380
- @ticket = format_notes_by_vulnerability(@ticket, full_summary)
381
- tickets.push(@ticket)
382
- redo
383
- end
384
- end
385
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
386
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
387
- @ticket = format_notes_by_vulnerability(@ticket, full_summary)
388
- tickets.push(@ticket) unless @ticket.nil?
389
- tickets
390
- end
391
-
392
- def format_notes_by_vulnerability(ticket, prepend)
393
- nxid_holder = ticket['Notes']
394
- ticket['Notes'] = ''
395
- ticket['Notes'] += prepend unless prepend.nil?
396
- ticket['Notes'] += ticket['Assets'] += "\n\n"
397
- ticket['Notes'] += ticket['Solutions'] += "\n\n"
398
- ticket['Notes'] += nxid_holder
399
- ticket.delete("Assets")
400
- ticket.delete("Solutions")
401
- ticket.delete("Full_Summary")
402
- ticket
403
- end
404
-
405
-
406
- # Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
407
- # currently only supports updating IP-address mode tickets in Remedy. The list of vulnerabilities
408
- # are ordered by IP address and then by ticket_status, allowing the method to loop through and
409
- # display new, old, and same vulnerabilities in that order.
410
- #
411
- # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
412
- #
413
- # * *Returns* :
414
- # - List of savon-formated (hash) tickets for updating within Remedy.
415
- #
416
- def prepare_update_tickets_by_ip(vulnerability_list, nexpose_identifier_id)
417
- fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'I'
215
+ def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
418
216
  @ticket = Hash.new(-1)
419
217
 
420
- @log.log_message('Preparing ticket updates by IP address.')
218
+ @log.log_message("Preparing tickets for #{@options[:ticket_mode]} mode.")
421
219
  tickets = []
422
- current_ip = -1
423
- ticket_status = 'New'
220
+ previous_row = nil
221
+ description = nil
424
222
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
425
- if current_ip == -1
426
- current_ip = row['ip_address']
427
- ticket_status = row['comparison']
223
+ if previous_row.nil?
224
+ previous_row = row.dup
225
+ description = @common_helper.get_description(nexpose_identifier_id, row)
226
+ @ticket = generate_new_ticket({'Summary' => "#{@common_helper.get_title(row, 100)}",
227
+ 'Notes' => ""})
228
+ #Skip querying for ticket if it's the initial scan
229
+ next if row['comparison'].nil?
428
230
 
429
231
  # Query Remedy for the incident by unique id (generated NXID)
430
- queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id}#{row['ip_address']}")
431
- if queried_incident.nil? || queried_incident.empty?
432
- @log.log_message("No incident found for NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}")
433
- else
434
- @log.log_message("Creating ticket update with IP address: #{row['ip_address']} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
435
- @log.log_message("Ticket status #{ticket_status}")
436
- # Remedy incident updates require populating all fields.
437
- @ticket = extract_queried_incident(queried_incident, "++ #{row['comparison']} Vulnerabilities ++++++++++++++++++++++++++\n")
438
- end
439
- end
440
- if current_ip == row['ip_address']
441
- # If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
442
- unless ticket_status == row['comparison']
443
- @ticket['Notes'] +=
444
- "\n\n\n++ #{row['comparison']} Vulnerabilities ++++++++++++++++++++++++++\n"
445
- ticket_status = row['comparison']
446
- end
447
-
448
- @ticket['Notes'] +=
449
- "\n\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}"
450
- # Only add the URL block if data exists in the row.
451
- unless row['url'].nil?
452
- @ticket['Notes'] +=
453
- "\nURL: #{row['url']}"
232
+ queried_incident = query_for_ticket("NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}")
233
+ if !queried_incident.nil? && queried_incident.first.is_a?(Hash)
234
+ queried_incident.select! { |t| !['Closed', 'Resolved', 'Cancelled'].include?(t[:status]) }
454
235
  end
455
- end
456
- unless current_ip == row['ip_address']
457
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
458
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}"
459
- tickets.push(@ticket)
460
- current_ip = -1
461
- redo
462
- end
463
- end
464
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
465
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_ip}" unless @ticket.empty?
466
- tickets.push(@ticket) unless @ticket.nil? || @ticket.empty?
467
- tickets
468
- end
469
-
470
- # Prepare ticket updates from the CSV of vulnerabilities exported from Nexpose. This method
471
- # currently only supports updating vulnerability mode tickets in Remedy. The list of vulnerabilities
472
- # are ordered by vulnerability ID and then by ticket_status, allowing the method to loop through and
473
- # display new, old, and same vulnerabilities in that order.
474
- #
475
- # - +vulnerability_list+ - CSV of vulnerabilities within Nexpose.
476
- #
477
- # * *Returns* :
478
- # - List of savon-formated (hash) tickets for updating within Remedy.
479
- #
480
- def prepare_update_tickets_by_vulnerability(vulnerability_list, nexpose_identifier_id)
481
- fail 'Ticket updates are only supported in IP-address mode.' if @options[:ticket_mode] != 'V'
482
- @ticket = Hash.new(-1)
483
-
484
- @log.log_message('Preparing ticket updates by IP address.')
485
- tickets = []
486
- current_vuln_id = -1
487
- current_solution_id = -1
488
- current_asset_id = -1
489
- current_solutions_text = "\n++ Details ++++++++++++++\n"
490
- ticket_status = 'New'
491
- CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
492
- if current_vuln_id == -1
493
- current_solutions_text = "\n++ Details +++++++++++++++\n"
494
- current_vuln_id = row['vulnerability_id']
495
- ticket_status = row['comparison']
496
- current_asset_id = -1
497
- current_solution_id = -1
498
-
499
- # Query Remedy for the incident by unique id (generated NXID)
500
- queried_incident = query_for_ticket("NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}")
501
236
  if queried_incident.nil? || queried_incident.empty?
502
- @log.log_message("No incident found for NXID: #{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}. Creating...")
237
+ @log.log_message("No incident found for NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}. Creating...")
503
238
  new_ticket_csv = vulnerability_list.split("\n").first
504
239
  new_ticket_csv += "\n#{row.to_s}"
505
- new_ticket = prepare_create_tickets_by_vulnerability(new_ticket_csv, nexpose_identifier_id)
240
+
241
+ #delete the comparison row
242
+ data = CSV::Table.new(CSV.parse(new_ticket_csv, headers: true))
243
+ data.delete("comparison")
244
+
245
+ new_ticket = prepare_create_tickets(data.to_s, nexpose_identifier_id)
506
246
  @log.log_message('Created ticket. Sending to Remedy...')
507
247
  create_tickets(new_ticket)
508
248
  @log.log_message('Ticket sent. Performing update for ticket...')
509
249
  #Now that there is a ticket for this NXID update it as if it existed this whole time...
510
- current_vuln_id = -1
250
+ previous_row = nil
511
251
  redo
512
252
  else
513
- @log.log_message("Creating ticket update for vulnerability with ID: #{row['vulnerability_id']}, Asset ID: #{row['asset_id']} and Nexpose Identifier ID: #{nexpose_identifier_id}. Ticket status #{ticket_status}.")
253
+ info = @common_helper.get_field_info(matching_fields, previous_row)
254
+ @log.log_message("Creating ticket update with #{info} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
255
+ @log.log_message("Ticket status #{row['comparison']}")
514
256
  # Remedy incident updates require populating all fields.
515
- @ticket = extract_queried_incident(queried_incident, "++ #{row['comparison']} Assets ++++++++++++++++++++++\n")
516
- end
517
- end
518
- if current_vuln_id == row['vulnerability_id']
519
- # If the ticket_status is different, add a a new 'header' to signify a new block of tickets.
520
- unless ticket_status == row['comparison']
521
- @ticket['Notes'] +=
522
- "\n\n\n++ #{row['comparison']} Assets +++++++++++++++++++++++\n"
523
- ticket_status = row['comparison']
524
- end
257
+ @ticket = extract_queried_incident(queried_incident, "")
258
+ end
259
+ elsif matching_fields.any? { |x| previous_row[x].nil? || previous_row[x] != row[x] }
260
+ info = @common_helper.get_field_info(matching_fields, previous_row)
261
+ @log.log_message("Generated ticket with #{info}")
525
262
 
526
- #Added the new asset to the list of affected systems if it is different (could have been the same asset with a different solution ID).
527
- if current_asset_id != row['asset_id']
528
- @ticket['Notes'] += "#{row['ip_address']}, "
529
- current_asset_id = row['asset_id']
530
- end
531
-
532
- #Add solutions for the now affected assets.
533
- if current_solution_id != row['solution_id']
534
- new_solution_text = "\n========================================== \nSummary: #{row['summary']} \nFix: #{row['fix']}\n"
535
- if current_solutions_text.include? new_solution_text
536
- @log.log_message('Ignoring duplicate solution for ticket update.')
537
- else
538
- current_solutions_text += new_solution_text
539
- #Add any references.
540
- unless row['url'].nil?
541
- current_solutions_text +=
542
- "\nURL: #{row['url']}"
543
- end
544
- end
545
- current_solution_id = row['solution_id']
546
- end
547
- end
548
- unless current_vuln_id == row['vulnerability_id']
549
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
550
- @ticket['Notes'] += "\n\n" + current_solutions_text
551
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}"
263
+ @ticket['Notes'] = @common_helper.print_description(description)
552
264
  tickets.push(@ticket)
553
- current_vuln_id = -1
554
- current_solution_id = -1
555
- current_asset_id = -1
265
+ previous_row = nil
266
+ description = nil
556
267
  redo
268
+ else
269
+ description = @common_helper.update_description(description, row)
557
270
  end
558
271
  end
559
- # NXID in the work_notes is the unique identifier used to query incidents to update them.
560
- @ticket['Notes'] += current_solutions_text
561
- @ticket['Notes'] += "\n\nNXID: #{nexpose_identifier_id}#{current_asset_id}#{current_vuln_id}" unless @ticket.empty?
562
- tickets.push(@ticket) unless @ticket.nil? || @ticket.empty?
272
+
273
+ unless @ticket.nil? || @ticket.empty?
274
+ @ticket['Notes'] = @common_helper.print_description(description)
275
+ tickets.push(@ticket)
276
+ end
277
+
278
+ @log.log_message("Generated <#{tickets.count.to_s}> tickets.")
563
279
  tickets
564
280
  end
565
281
 
282
+ # Creates a ticket with the extracted data from a queried Remedy incident.
283
+ #
284
+ # - +queried_incident+ - The queried incident from Remedy
285
+ # - +notes_header+ - The texted to be placed at the top of the Remedy 'Notes' field.
286
+ # - +status+ - The status to which the ticket will be set.
287
+ #
288
+ # * *Returns* :
289
+ # - A single savon-formated (hash) ticket for updating within Remedy.
290
+ #
291
+ def ticket_from_queried_incident(queried_incident, notes_header, status)
292
+ {
293
+ 'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
294
+ 'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
295
+ 'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
296
+ 'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
297
+ 'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
298
+ 'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
299
+ 'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
300
+ 'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
301
+ 'Closure_Product_Name' => queried_incident[:closure_product_name],
302
+ 'Company' => queried_incident[:company],
303
+ 'Summary' => queried_incident[:summary],
304
+ 'Notes' => notes_header || queried_incident[:notes],
305
+ 'Impact' => queried_incident[:impact],
306
+ 'Manufacturer' => queried_incident[:manufacturer],
307
+ 'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
308
+ 'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
309
+ 'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
310
+ 'Product_Model_Version' => queried_incident[:product_model_version],
311
+ 'Product_Name' => queried_incident[:product_name],
312
+ 'Reported_Source' => queried_incident[:reported_source],
313
+ 'Resolution' => queried_incident[:resolution],
314
+ 'Resolution_Category' => queried_incident[:resolution_category],
315
+ 'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
316
+ 'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
317
+ 'Resolution_Method' => queried_incident[:resolution_method],
318
+ 'Service_Type' => queried_incident[:service_type],
319
+ 'Status' => status || queried_incident[:status],
320
+ 'Urgency' => queried_incident[:urgency],
321
+ 'Action' => 'MODIFY',
322
+ 'Work_Info_Summary' => queried_incident[:work_info_summary],
323
+ 'Work_Info_Notes' => queried_incident[:work_info_notes],
324
+ 'Work_Info_Type' => queried_incident[:work_info_type],
325
+ 'Work_Info_Date' => queried_incident[:work_info_date],
326
+ 'Work_Info_Source' => queried_incident[:work_info_source],
327
+ 'Work_Info_Locked' => queried_incident[:work_info_locked],
328
+ 'Work_Info_View_Access' => queried_incident[:work_info_view_access],
329
+ 'Incident_Number' => queried_incident[:incident_number],
330
+ 'Status_Reason' => queried_incident[:status_reason],
331
+ 'ServiceCI' => queried_incident[:service_ci],
332
+ 'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
333
+ 'HPD_CI' => queried_incident[:hpd_ci],
334
+ 'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
335
+ 'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
336
+ 'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
337
+ }
338
+ end
339
+
566
340
  # Extracts from a queried Remedy incident the relevant data required for an update to be made to said incident.
567
341
  # Creates a ticket with the extracted data.
568
342
  #
@@ -573,116 +347,13 @@ class RemedyHelper
573
347
  # - A single savon-formated (hash) ticket for updating within Remedy.
574
348
  #
575
349
  def extract_queried_incident(queried_incident, notes_header)
576
-
577
- if queried_incident[0].is_a?(Hash)
578
- #Hash of hashes
579
- @log.log_message("More than one ticket returned from Remedy. Number of tickets returned: #{queried_incident.count}. Parsing return to check returned ticket status...")
580
- @ticket = nil
581
- queried_incident.count.times do |x|
582
- #TODO: This should be moved to the config for the Remedy helper.
583
- if ['Closed', 'Resolved', 'Cancelled'].include? queried_incident[x][:status]
584
- @log.log_message("Returned ticket number <#{x}> of <#{(queried_incident.count - 1)}> is of status <#{queried_incident[x][:status]}>. Ignoring.")
585
- else
586
- fail 'When trying to update tickets Remedy returned multiple tickets with the same NXID and in progress status!' unless @ticket.nil?
587
- @ticket = {
588
- 'Categorization_Tier_1' => queried_incident[x][:categorization_tier_1],
589
- 'Categorization_Tier_2' => queried_incident[x][:categorization_tier_2],
590
- 'Categorization_Tier_3' => queried_incident[x][:categorization_tier_3],
591
- 'Closure_Manufacturer' => queried_incident[x][:closure_manufacturer],
592
- 'Closure_Product_Category_Tier1' => queried_incident[x][:closure_product_category_tier1],
593
- 'Closure_Product_Category_Tier2' => queried_incident[x][:closure_product_category_tier2],
594
- 'Closure_Product_Category_Tier3' => queried_incident[x][:closure_product_category_tier3],
595
- 'Closure_Product_Model_Version' => queried_incident[x][:closure_product_model_version],
596
- 'Closure_Product_Name' => queried_incident[x][:closure_product_name],
597
- 'Company' => queried_incident[x][:company],
598
- 'Summary' => queried_incident[x][:summary],
599
- 'Notes' => notes_header,
600
- 'Impact' => queried_incident[x][:impact],
601
- 'Manufacturer' => queried_incident[x][:manufacturer],
602
- 'Product_Categorization_Tier_1' => queried_incident[x][:product_categorization_tier_1],
603
- 'Product_Categorization_Tier_2' => queried_incident[x][:product_categorization_tier_2],
604
- 'Product_Categorization_Tier_3' => queried_incident[x][:product_categorization_tier_3],
605
- 'Product_Model_Version' => queried_incident[x][:product_model_version],
606
- 'Product_Name' => queried_incident[x][:product_name],
607
- 'Reported_Source' => queried_incident[x][:reported_source],
608
- 'Resolution' => queried_incident[x][:resolution],
609
- 'Resolution_Category' => queried_incident[x][:resolution_category],
610
- 'Resolution_Category_Tier_2' => queried_incident[x][:resolution_category_tier_2],
611
- 'Resolution_Category_Tier_3' => queried_incident[x][:resolution_category_tier_3],
612
- 'Resolution_Method' => queried_incident[x][:resolution_method],
613
- 'Service_Type' => queried_incident[x][:service_type],
614
- 'Status' => queried_incident[x][:status],
615
- 'Urgency' => queried_incident[x][:urgency],
616
- 'Action' => 'MODIFY',
617
- 'Work_Info_Summary' => queried_incident[x][:work_info_summary],
618
- 'Work_Info_Notes' => queried_incident[x][:work_info_notes],
619
- 'Work_Info_Type' => queried_incident[x][:work_info_type],
620
- 'Work_Info_Date' => queried_incident[x][:work_info_date],
621
- 'Work_Info_Source' => queried_incident[x][:work_info_source],
622
- 'Work_Info_Locked' => queried_incident[x][:work_info_locked],
623
- 'Work_Info_View_Access' => queried_incident[x][:work_info_view_access],
624
- 'Incident_Number' => queried_incident[x][:incident_number],
625
- 'Status_Reason' => queried_incident[x][:status_reason],
626
- 'ServiceCI' => queried_incident[x][:service_ci],
627
- 'ServiceCI_ReconID' => queried_incident[x][:service_ci_recon_id],
628
- 'HPD_CI' => queried_incident[x][:hpd_ci],
629
- 'HPD_CI_ReconID' => queried_incident[x][:hpd_ci_recon_id],
630
- 'HPD_CI_FormName' => queried_incident[x][:hpd_ci_form_name],
631
- 'z1D_CI_FormName' => queried_incident[x][:z1d_ci_form_name]
632
- }
633
- end
634
- end
635
- else
636
- @ticket = {
637
- 'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
638
- 'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
639
- 'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
640
- 'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
641
- 'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
642
- 'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
643
- 'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
644
- 'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
645
- 'Closure_Product_Name' => queried_incident[:closure_product_name],
646
- 'Company' => queried_incident[:company],
647
- 'Summary' => queried_incident[:summary],
648
- 'Notes' => notes_header,
649
- 'Impact' => queried_incident[:impact],
650
- 'Manufacturer' => queried_incident[:manufacturer],
651
- 'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
652
- 'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
653
- 'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
654
- 'Product_Model_Version' => queried_incident[:product_model_version],
655
- 'Product_Name' => queried_incident[:product_name],
656
- 'Reported_Source' => queried_incident[:reported_source],
657
- 'Resolution' => queried_incident[:resolution],
658
- 'Resolution_Category' => queried_incident[:resolution_category],
659
- 'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
660
- 'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
661
- 'Resolution_Method' => queried_incident[:resolution_method],
662
- 'Service_Type' => queried_incident[:service_type],
663
- 'Status' => queried_incident[:status],
664
- 'Urgency' => queried_incident[:urgency],
665
- 'Action' => 'MODIFY',
666
- 'Work_Info_Summary' => queried_incident[:work_info_summary],
667
- 'Work_Info_Notes' => queried_incident[:work_info_notes],
668
- 'Work_Info_Type' => queried_incident[:work_info_type],
669
- 'Work_Info_Date' => queried_incident[:work_info_date],
670
- 'Work_Info_Source' => queried_incident[:work_info_source],
671
- 'Work_Info_Locked' => queried_incident[:work_info_locked],
672
- 'Work_Info_View_Access' => queried_incident[:work_info_view_access],
673
- 'Incident_Number' => queried_incident[:incident_number],
674
- 'Status_Reason' => queried_incident[:status_reason],
675
- 'ServiceCI' => queried_incident[:service_ci],
676
- 'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
677
- 'HPD_CI' => queried_incident[:hpd_ci],
678
- 'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
679
- 'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
680
- 'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
681
- }
350
+ unless queried_incident.first.is_a?(Hash)
351
+ return ticket_from_queried_incident(queried_incident, notes_header, nil)
682
352
  end
683
- @ticket
684
- end
685
353
 
354
+ fail "Multiple tickets returned for same NXID" if queried_incidents.count > 1
355
+ ticket_from_queried_incident(queried_incident.first, notes_header, nil)
356
+ end
686
357
 
687
358
  # Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.
688
359
  #
@@ -693,79 +364,22 @@ class RemedyHelper
693
364
  # - List of savon-formated (hash) tickets for closing within Remedy.
694
365
  #
695
366
  def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
696
- fail 'Ticket closures are only supported in default mode.' if @options[:ticket_mode] == 'I'
697
- @log.log_message('Preparing ticket closures by default method.')
367
+ @log.log_message("Preparing ticket closures for mode #{@options[:ticket_mode]}.")
698
368
  @nxid = nil
699
369
  tickets = []
700
370
  CSV.parse(vulnerability_list.chomp, headers: :first_row) do |row|
701
- case @options[:ticket_mode]
702
- # 'D' Default mode: IP *-* Vulnerability
703
- when 'D'
704
- @nxid = "#{nexpose_identifier_id}#{row['asset_id']}#{row['vulnerability_id']}#{row['solution_id']}"
705
- # 'I' IP address mode: IP address -* Vulnerability
706
- when 'I'
707
- @nxid = "#{nexpose_identifier_id}#{row['current_ip']}"
708
- # 'V' Vulnerability mode: Vulnerability -* IP address
709
- when 'V'
710
- @nxid = "#{nexpose_identifier_id}#{row['current_asset_id']}#{row['current_vuln_id']}"
711
- else
712
- fail 'Could not close tickets - do not understand the ticketing mode!'
713
- end
371
+ @nxid = @common_helper.generate_nxid(nexpose_identifier_id, row)
372
+
714
373
  # Query Remedy for the incident by unique id (generated NXID)
715
374
  queried_incident = query_for_ticket("NXID: #{@nxid}")
716
375
  if queried_incident.nil? || queried_incident.empty?
717
376
  @log.log_message("No incident found for NXID: #{@nxid}")
718
377
  else
719
378
  # Remedy incident updates require populating all fields.
720
- ticket = {
721
- 'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
722
- 'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
723
- 'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
724
- 'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
725
- 'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
726
- 'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
727
- 'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
728
- 'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
729
- 'Closure_Product_Name' => queried_incident[:closure_product_name],
730
- 'Company' => queried_incident[:company],
731
- 'Summary' => queried_incident[:summary],
732
- 'Notes' => queried_incident[:notes],
733
- 'Impact' => queried_incident[:impact],
734
- 'Manufacturer' => queried_incident[:manufacturer],
735
- 'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
736
- 'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
737
- 'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
738
- 'Product_Model_Version' => queried_incident[:product_model_version],
739
- 'Product_Name' => queried_incident[:product_name],
740
- 'Reported_Source' => queried_incident[:reported_source],
741
- 'Resolution' => queried_incident[:resolution],
742
- 'Resolution_Category' => queried_incident[:resolution_category],
743
- 'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
744
- 'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
745
- 'Resolution_Method' => queried_incident[:resolution_method],
746
- 'Service_Type' => queried_incident[:service_type],
747
- 'Status' => 'Closed',
748
- 'Urgency' => queried_incident[:urgency],
749
- 'Action' => 'MODIFY',
750
- 'Work_Info_Summary' => queried_incident[:work_info_summary],
751
- 'Work_Info_Notes' => queried_incident[:work_info_notes],
752
- 'Work_Info_Type' => queried_incident[:work_info_type],
753
- 'Work_Info_Date' => queried_incident[:work_info_date],
754
- 'Work_Info_Source' => queried_incident[:work_info_source],
755
- 'Work_Info_Locked' => queried_incident[:work_info_locked],
756
- 'Work_Info_View_Access' => queried_incident[:work_info_view_access],
757
- 'Incident_Number' => queried_incident[:incident_number],
758
- 'Status_Reason' => queried_incident[:status_reason],
759
- 'ServiceCI' => queried_incident[:service_ci],
760
- 'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
761
- 'HPD_CI' => queried_incident[:hpd_ci],
762
- 'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
763
- 'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
764
- 'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
765
- }
379
+ ticket = ticket_from_queried_incident(queried_incident, nil, 'Closed')
766
380
  tickets.push(ticket)
767
381
  end
768
382
  end
769
383
  tickets
770
384
  end
771
- end
385
+ end