Class: RemedyHelper

Inherits:
Object
  • Object
show all
Defined in:
lib/nexpose_ticketing/helpers/remedy_helper.rb

Overview

Serves as the Remedy interface for creating/updating issues from vulnelrabilities found in Nexpose.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(remedy_data, options) ⇒ RemedyHelper

Returns a new instance of RemedyHelper.



15
16
17
18
19
20
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 15

def initialize(remedy_data, options)
  @remedy_data = remedy_data
  @options = options
  @log = NexposeTicketing::NxLogger.instance
  @common_helper = NexposeTicketing::CommonHelper.new(@options)
end

Instance Attribute Details

#clientObject

Returns the value of attribute client.



14
15
16
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 14

def client
  @client
end

#logObject

Returns the value of attribute log.



14
15
16
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 14

def log
  @log
end

#optionsObject

Returns the value of attribute options.



14
15
16
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 14

def options
  @options
end

#remedy_dataObject

Returns the value of attribute remedy_data.



14
15
16
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 14

def remedy_data
  @remedy_data
end

Instance Method Details

#close_tickets(tickets) ⇒ Object

Sends ticket closure (in SOAP format) to Remedy individually (each ticket in the list as a separate web service call).

  • Args :

    • tickets - List of savon-formatted (hash) ticket closures.



123
124
125
126
127
128
129
130
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 123

def close_tickets(tickets)
  if tickets.nil? || tickets.empty?
    @log.log_message("No tickets to close.")
    return
  end
  client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
  send_tickets(client, :help_desk_modify_service, tickets)
end

#create_tickets(tickets) ⇒ Object

Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list as a separate web service call).

  • Args :

    • tickets - List of savon-formatted (hash) ticket creates (new tickets).



96
97
98
99
100
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 96

def create_tickets(tickets)
  fail 'Ticket(s) cannot be empty' if tickets.nil? || tickets.empty?
  client = get_client('HPD_IncidentInterface_Create_WS.xml', :create_soap_endpoint)
  send_tickets(client, :help_desk_submit_service, tickets)
end

#extract_queried_incident(queried_incident, notes_header) ⇒ Object

Extracts from a queried Remedy incident the relevant data required for an update to be made to said incident. Creates a ticket with the extracted data.

- +queried_incident+ - The queried incident from Remedy
- +notes_header+ - The texted to be placed at the top of the Remedy 'Notes' field.
  • Returns :

    • A single savon-formated (hash) ticket for updating within Remedy.



349
350
351
352
353
354
355
356
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 349

def extract_queried_incident(queried_incident, notes_header)
  unless queried_incident.first.is_a?(Hash)
    return ticket_from_queried_incident(queried_incident, notes_header, nil) 
  end

  fail "Multiple tickets returned for same NXID" if queried_incidents.count > 1 
  ticket_from_queried_incident(queried_incident.first, notes_header, nil)
end

#generate_new_ticket(extra_fields = nil) ⇒ Object

Generates a savon-based ticket object.

  • Args :

    • extra_fields - List of mode-specific fields (hash) to be added to the ticket.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 27

def generate_new_ticket(extra_fields=nil)
  base_ticket = {
    'First_Name' => "#{@remedy_data[:first_name]}",
      'Impact' => '1-Extensive/Widespread',
      'Last_Name' => "#{@remedy_data[:last_name]}",
      'Reported_Source' => 'Other',
      'Service_Type' => 'Infrastructure Event',
      'Status' => 'New',
      'Action' => 'CREATE',
      "Summary"=>"",
      "Notes"=>"",
      'Urgency' => '1-Critical',
  }
  extra_fields.each { |k, v| base_ticket[k.to_s] = v } if extra_fields
  base_ticket
end

#get_client(wdsl, endpoint) ⇒ Object

Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list as a separate web service call).

  • Args :

    • wdsl - XML file which describes the network service.

    • endpoint - Endpoint to which the data will be submitted.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 51

def get_client(wdsl, endpoint)
  Savon.client(wsdl:  File.join(File.dirname(__FILE__), "../config/remedy_wsdl/#{wdsl}"),
               adapter: :net_http,
               ssl_verify_mode: :none,
               open_timeout: @remedy_data[:open_timeout],
               read_timeout: @remedy_data[:read_timeout],
               endpoint: @remedy_data[endpoint.intern],
               soap_header: { 'AuthenticationInfo' => 
                                { 'userName' => "#{@remedy_data[:username]}",
                                  'password' => "#{@remedy_data[:password]}",
                                  'authentication' => "#{@remedy_data[:authentication]}"
                                 }
                            })
end

#prepare_close_tickets(vulnerability_list, nexpose_identifier_id) ⇒ Object

Prepare ticket closures from the CSV of vulnerabilities exported from Nexpose.

  • Args :

    • vulnerability_list - CSV of vulnerabilities within Nexpose.

  • Returns :

    • List of savon-formated (hash) tickets for closing within Remedy.



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 366

def prepare_close_tickets(vulnerability_list, nexpose_identifier_id)
  @log.log_message("Preparing ticket closures for mode #{@options[:ticket_mode]}.")
  @nxid = nil
  tickets = []
  CSV.parse(vulnerability_list.chomp, headers: :first_row)  do |row|
    @nxid = @common_helper.generate_nxid(nexpose_identifier_id, row)

    # Query Remedy for the incident by unique id (generated NXID)
    queried_incident = query_for_ticket("NXID: #{@nxid}")
    if queried_incident.nil? || queried_incident.empty?
      @log.log_message("No incident found for NXID: #{@nxid}")
    else
      # Remedy incident updates require populating all fields.
      ticket = ticket_from_queried_incident(queried_incident, nil, 'Closed')
      tickets.push(ticket)
    end
  end
  tickets
end

#prepare_create_tickets(vulnerability_list, nexpose_identifier_id) ⇒ Object

Prepare tickets from the CSV of vulnerabilities exported from Nexpose. This method determines how to prepare the tickets (by default, by IP address or by vulnerability) based on config options.

  • Args :

    • vulnerability_list - CSV of vulnerabilities within Nexpose.

  • Returns :

    • List of savon-formated (hash) tickets for creating within Remedy.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 166

def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
  @log.log_message('Preparing ticket requests...')
  case @options[:ticket_mode]
  # 'D' Default IP *-* Vulnerability
  when 'D' then matching_fields = ['ip_address', 'vulnerability_id']
  # 'I' IP address -* Vulnerability
  when 'I' then matching_fields = ['ip_address']
  # 'V' Vulnerability -* Assets
  when 'V' then matching_fields = ['vulnerability_id']
  else
      fail 'Unsupported ticketing mode selected.'
  end

  prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
end

#prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields) ⇒ Object

Prepares a list of vulnerabilities into a list of savon-formatted tickets (incidents) for Remedy.

  • Args :

    • vulnerability_list - CSV of vulnerabilities within Nexpose.

  • Returns :

    • List of savon-formated (hash) tickets for creating within Remedy.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 215

def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
  @ticket = Hash.new(-1)
  
  @log.log_message("Preparing tickets for #{@options[:ticket_mode]} mode.")
  tickets = []
  previous_row = nil
  description = nil
  CSV.parse(vulnerability_list.chomp, headers: :first_row)  do |row|
    if previous_row.nil?
      previous_row = row.dup        
      description = @common_helper.get_description(nexpose_identifier_id, row)
      @ticket = generate_new_ticket({'Summary' => "#{@common_helper.get_title(row, 100)}",
                                      'Notes' => ""})
      #Skip querying for ticket if it's the initial scan
      next if row['comparison'].nil?
      
      # Query Remedy for the incident by unique id (generated NXID)
      queried_incident = query_for_ticket("NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}")
      if !queried_incident.nil? && queried_incident.first.is_a?(Hash)
        queried_incident.select! { |t| !['Closed', 'Resolved', 'Cancelled'].include?(t[:status]) }
      end
      if queried_incident.nil? || queried_incident.empty?
        @log.log_message("No incident found for NXID: #{@common_helper.generate_nxid(nexpose_identifier_id, row)}. Creating...")
        new_ticket_csv = vulnerability_list.split("\n").first
        new_ticket_csv += "\n#{row.to_s}"
        
        #delete the comparison row
        data = CSV::Table.new(CSV.parse(new_ticket_csv, headers: true))
        data.delete("comparison")

        new_ticket = prepare_create_tickets(data.to_s, nexpose_identifier_id)
        @log.log_message('Created ticket. Sending to Remedy...')
        create_tickets(new_ticket)
        @log.log_message('Ticket sent. Performing update for ticket...')
        #Now that there is a ticket for this NXID update it as if it existed this whole time...
        previous_row = nil
        redo
      else
        info = @common_helper.get_field_info(matching_fields, previous_row)
        @log.log_message("Creating ticket update with #{info} for Nexpose Identifier with ID: #{nexpose_identifier_id}")
        @log.log_message("Ticket status #{row['comparison']}")
        # Remedy incident updates require populating all fields.
        @ticket = extract_queried_incident(queried_incident, "")
      end   
    elsif matching_fields.any? { |x| previous_row[x].nil? || previous_row[x] != row[x] }
      info = @common_helper.get_field_info(matching_fields, previous_row)
      @log.log_message("Generated ticket with #{info}")

      @ticket['Notes'] = @common_helper.print_description(description)
      tickets.push(@ticket)
      previous_row = nil
      description = nil
      redo
    else
      description = @common_helper.update_description(description, row)        
    end
  end

  unless @ticket.nil? || @ticket.empty?
    @ticket['Notes'] = @common_helper.print_description(description)
    tickets.push(@ticket)
  end

  @log.log_message("Generated <#{tickets.count.to_s}> tickets.")
  tickets
end

#prepare_update_tickets(vulnerability_list, nexpose_identifier_id) ⇒ Object

Prepare to update tickets from the CSV of vulnerabilities exported from Nexpose. This method determines how to prepare the tickets for update (by IP address or by vulnerability) based on config options.

  • Args :

    • vulnerability_list - CSV of vulnerabilities within Nexpose.

  • Returns :

    • List of savon-formated (hash) tickets for creating within Remedy.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 191

def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
  case @options[:ticket_mode]
  # 'D' Default IP *-* Vulnerability
  when 'D' then fail 'Ticket updates are not supported in Default mode.'
  # 'I' IP address -* Vulnerability
  when 'I' then matching_fields = ['ip_address']
  # 'V' Vulnerability -* Assets
  when 'V' then matching_fields = ['vulnerability_id']
  else
      fail 'Unsupported ticketing mode selected.'
  end

  prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
end

#query_for_ticket(unique_id) ⇒ Object

Sends a query (in SOAP format) to Remedy to return back a single ticket based on the criteria.

  • Args :

    • unique_id - Unique identifier generated by the helper.

  • Returns :

    • Remedy incident information in hash format or nil if no results are found.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 140

def query_for_ticket(unique_id)
  client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)

  begin
    response = client.call(:help_desk_query_list_service, message: {'Qualification' => "'Status' < \"Closed\" AND 'Detailed Decription' LIKE \"%#{unique_id}%\""})
  rescue Savon::SOAPFault => e
    @log.log_message("SOAP exception in query ticket: #{e.message}")
    return if e.to_hash[:fault][:faultstring].index("ERROR (302)") == 0
    raise
  rescue Savon::HTTPError => e
    @log.log_message("HTTP error in query ticket: #{e.message}")
    raise
  end
  
  response.body[:help_desk_query_list_service_response][:get_list_values]
end

#send_tickets(client, service, tickets) ⇒ Object

Sends a list of tickets (in SOAP format) to Remedy individually (each ticket in the list as a separate web service call).

  • Args :

    • service - The helpdesk service to which the tickets should be submitted.

    • tickets - List of savon-formatted (hash) ticket creates (new tickets).



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 73

def send_tickets(client, service, tickets)
  service_name = service.to_s.match(/desk_([a-z]*)_/)
  service_name = service_name.captures.first unless service_name.nil?
  tickets.each do |ticket|
    begin
      @log.log_message(ticket)
      response = client.call(service, message: ticket)
    rescue Savon::SOAPFault => e
      @log.log_message("SOAP exception in #{service_name} ticket: #{e.message}")
      raise
    rescue Savon::HTTPError => e
      @log.log_message("HTTP error in #{service_name} ticket: #{e.message}")
      raise
    end
  end
end

#ticket_from_queried_incident(queried_incident, notes_header, status) ⇒ Object

Creates a ticket with the extracted data from a queried Remedy incident.

- +queried_incident+ - The queried incident from Remedy
- +notes_header+ - The texted to be placed at the top of the Remedy 'Notes' field.
- +status+ - The status to which the ticket will be set.
  • Returns :

    • A single savon-formated (hash) ticket for updating within Remedy.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 291

def ticket_from_queried_incident(queried_incident, notes_header, status)
  {
    'Categorization_Tier_1' => queried_incident[:categorization_tier_1],
    'Categorization_Tier_2' => queried_incident[:categorization_tier_2],
    'Categorization_Tier_3' => queried_incident[:categorization_tier_3],
    'Closure_Manufacturer' => queried_incident[:closure_manufacturer],
    'Closure_Product_Category_Tier1' => queried_incident[:closure_product_category_tier1],
    'Closure_Product_Category_Tier2' => queried_incident[:closure_product_category_tier2],
    'Closure_Product_Category_Tier3' => queried_incident[:closure_product_category_tier3],
    'Closure_Product_Model_Version' => queried_incident[:closure_product_model_version],
    'Closure_Product_Name' => queried_incident[:closure_product_name],
    'Company' => queried_incident[:company],
    'Summary' => queried_incident[:summary],
    'Notes' => notes_header || queried_incident[:notes],
    'Impact' => queried_incident[:impact],
    'Manufacturer' => queried_incident[:manufacturer],
    'Product_Categorization_Tier_1' => queried_incident[:product_categorization_tier_1],
    'Product_Categorization_Tier_2' => queried_incident[:product_categorization_tier_2],
    'Product_Categorization_Tier_3' => queried_incident[:product_categorization_tier_3],
    'Product_Model_Version' => queried_incident[:product_model_version],
    'Product_Name' => queried_incident[:product_name],
    'Reported_Source' => queried_incident[:reported_source],
    'Resolution' => queried_incident[:resolution],
    'Resolution_Category' => queried_incident[:resolution_category],
    'Resolution_Category_Tier_2' => queried_incident[:resolution_category_tier_2],
    'Resolution_Category_Tier_3' => queried_incident[:resolution_category_tier_3],
    'Resolution_Method' => queried_incident[:resolution_method],
    'Service_Type' => queried_incident[:service_type],
    'Status' => status || queried_incident[:status],
    'Urgency' => queried_incident[:urgency],
    'Action' => 'MODIFY',
    'Work_Info_Summary' => queried_incident[:work_info_summary],
    'Work_Info_Notes' => queried_incident[:work_info_notes],
    'Work_Info_Type' => queried_incident[:work_info_type],
    'Work_Info_Date' => queried_incident[:work_info_date],
    'Work_Info_Source' => queried_incident[:work_info_source],
    'Work_Info_Locked' => queried_incident[:work_info_locked],
    'Work_Info_View_Access' => queried_incident[:work_info_view_access],
    'Incident_Number' => queried_incident[:incident_number],
    'Status_Reason' => queried_incident[:status_reason],
    'ServiceCI' => queried_incident[:service_ci],
    'ServiceCI_ReconID' => queried_incident[:service_ci_recon_id],
    'HPD_CI' => queried_incident[:hpd_ci],
    'HPD_CI_ReconID' => queried_incident[:hpd_ci_recon_id],
    'HPD_CI_FormName' => queried_incident[:hpd_ci_form_name],
    'z1D_CI_FormName' => queried_incident[:z1d_ci_form_name]
  }
end

#update_tickets(tickets) ⇒ Object

Sends ticket updates (in SOAP format) to Remedy individually (each ticket in the list as a separate web service call).

  • Args :

    • tickets - List of savon-formatted (hash) ticket updates.



108
109
110
111
112
113
114
115
# File 'lib/nexpose_ticketing/helpers/remedy_helper.rb', line 108

def update_tickets(tickets)
  if tickets.nil? || tickets.empty?
    @log.log_message("No tickets to update.")
    return
  end
  client = get_client('HPD_IncidentInterface_WS.xml', :query_modify_soap_endpoint)
  send_tickets(client, :help_desk_modify_service, tickets)
end