Class: ServiceDeskHelper

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(servicedesk_data, options) ⇒ ServiceDeskHelper

Returns a new instance of ServiceDeskHelper.



11
12
13
14
15
16
17
18
19
20
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 11

def initialize(servicedesk_data, options)
  @servicedesk_data = servicedesk_data
  @options = options
  @log = NexposeTicketing::NxLogger.instance

  @rest_uri = servicedesk_data[:rest_uri]
  @api_key = servicedesk_data[:api_key]
  @ticket_db_path = servicedesk_data[:ticket_db_path]
  @common_helper = NexposeTicketing::CommonHelper.new(@options)
end

Instance Attribute Details

#logObject

Returns the value of attribute log.



9
10
11
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 9

def log
  @log
end

#optionsObject

Returns the value of attribute options.



9
10
11
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 9

def options
  @options
end

#servicedesk_dataObject

Returns the value of attribute servicedesk_data.



9
10
11
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 9

def servicedesk_data
  @servicedesk_data
end

Instance Method Details

#add_ticket_to_database(workorderid, nxid) ⇒ Object



28
29
30
31
32
33
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 28

def add_ticket_to_database(workorderid, nxid)
  @log.log_message("Adding ticket <#{workorderid}> for NXID <#{nxid}> to local db.")
  db = open_database()
  db[nxid] = workorderid
  db.close()
end

#close_ticket(ticket) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 268

def close_ticket(ticket)
  @log.log_message("Connecting to #{@rest_uri}/#{ticket[:workorderid]}")
  uri = URI( "#{@rest_uri}/#{ticket[:workorderid]}" )
  res = Net::HTTP::post_form(uri,
                             'OPERATION_NAME' => 'CLOSE_REQUEST',
                             'TECHNICIAN_KEY' => @api_key)

  response = Nokogiri::XML.parse(res.read_body)
  begin
    status = Integer(response.xpath('//statuscode').text)
  rescue Exception => e
    @log.log_message("XML request was #{ticket[:description]} response is #{response.to_xml}")
    raise e
  end

  unless status == 200
    @log.log_message("Unable to close ticket #{ticket}, got response #{response.to_xml}")
  end
end

#close_tickets(tickets) ⇒ Object



355
356
357
358
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 355

def close_tickets( tickets )
  tickets.each { |ticket| close_ticket(ticket) if ticket[:action] == :close && !ticket[:workorderid].nil?}
  remove_tickets_from_database(tickets)
end

#create_ticket_request(subject, description) ⇒ Object

Uses the configured or default options to set up a ticket creation request



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 139

def create_ticket_request(subject, description)
  request = Nokogiri::XML::Builder.new do |xml|
    xml.Operation {
      xml.Details {
        xml.parameter {
          xml.name {
              xml.text 'requester'
          }
          xml.value {
            xml.text @servicedesk_data[:requester]
          }
        }
        xml.parameter {
          xml.name {
            xml.text 'Group'
          }
          xml.value {
            xml.text @servicedesk_data[:group]
          }
        }
        xml.parameter {
          xml.name {
            xml.text 'subject'
          }
          xml.value {
            xml.text subject
          }
        }
        xml.parameter {
          xml.name {
            xml.text 'description'
          }
          xml.value {
            xml.cdata description
          }
        }
      }
    }
  end
  request.to_xml
end

#create_tickets(tickets) ⇒ Object



289
290
291
292
293
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 289

def create_tickets(tickets)
  @log.log_message("Creating tickets on server at #{@rest_uri}")

  tickets.each { |ticket| submit_ticket(ticket) }
end

#find_ticket_in_database(nxid) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 36

def find_ticket_in_database(nxid) 
  @log.log_message("Finding workorder id for NXID <#{nxid}> from local db.")
  db = open_database()
  begin
    workorderid = db[nxid]
    @log.log_message("Lookup found incident <#{workorderid}> in the db.")
  rescue Exception => e
    @log.log_message("Threw an exception accessing the dbm <#{e.class} #{e} #{e.message}>.")
    raise e
  end
  db.close()

  workorderid
end

#modify_ticket(ticket) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 246

def modify_ticket(ticket)
  @log.log_message("Connecting to #{@rest_uri}/#{ticket[:workorderid]}")
  uri = URI( "#{@rest_uri}/#{ticket[:workorderid]}")
  res = Net::HTTP::post_form(uri,
                             'OPERATION_NAME' => 'EDIT_REQUEST',
                             'TECHNICIAN_KEY' => @api_key,
                             'INPUT_DATA' => ticket[:description])

  response = Nokogiri::XML.parse(res.read_body)
  begin
    status = Integer(response.xpath('//statuscode').text)
  rescue Exception => e
    @log.log_message("XML request was #{ticket[:description]} response is #{response.to_xml}")
    raise e
  end
  
  unless status == 200
    @log.log_message("Unable to modify ticket #{ticket}, got response #{response.to_xml}")
  end
end

#modify_ticket_request(description) ⇒ Object



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 181

def modify_ticket_request(description)
#         modifyRequest = """
# <Operation>
#     <Details>
#         <parameter>
#             <name>description</name>
#             <value>#{description}</value>
#         </parameter>
#     </Details>
# </Operation>
# """
  doc = Nokogiri::XML::Builder.new() do |xml|
    xml.Operation {
      xml.Details {
        xml.parameter {
          xml.name {
              xml.text 'requester'
          }
          xml.value {
            xml.text @servicedesk_data[:requester]
          }
        }
        xml.parameter {
          xml.name {
            xml.text 'description'
          }
          xml.value {
            xml.cdata description
          }
        }
      }
    }
  end
  doc.to_xml
end

#open_databaseObject



23
24
25
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 23

def open_database()
  DBM.open(@ticket_db_path, 0600, DBM::WRCREAT)
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 ServiceDesk.



334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 334

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)

    workorderid = find_ticket_in_database(@nxid)
    # Query ServiceDesk for the incident by unique id (generated NXID)
    if workorderid.nil? || workorderid.empty?
      @log.log_message("No workorderid found for NXID #{@nxid}")
    else
      tickets << { :action => :close, :nxid => @nxid, 
                   :workorderid => workorderid,
                   :description => 'Automatically closing ticket.' }
    end
  end
  tickets
end

#prepare_create_tickets(vulnerability_list, nexpose_identifier_id) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 63

def prepare_create_tickets(vulnerability_list, nexpose_identifier_id)
  @log.log_message('Preparing ticket creation...')
  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

  tickets = prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)

  tickets.each { |ticket| @log.log_message("Prepared ticket: #{ticket}")}
  tickets
end

#prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 83

def prepare_tickets(vulnerability_list, nexpose_identifier_id, matching_fields)
  @log.log_message("Preparing ticket for #{@options[:ticket_mode]} mode.")
  tickets = []
  host_vulns={}
  previous_row = nil
  description = nil
  nxid = nil

  initial_scan = false

  CSV.parse( vulnerability_list.chomp, headers: :first_row )  do |row|
    initial_scan = initial_scan || row['comparison'].nil?
      
    if previous_row.nil?
      nxid = @common_helper.generate_nxid(nexpose_identifier_id, row)
      previous_row = row.dup
      description = @common_helper.get_description(nexpose_identifier_id, row)  

      host_vulns[nxid] = { :ip => row['ip_address'], 
                           :description => "",
                           :title => @common_helper.get_title(row) } 
    elsif matching_fields.any? {  |x| previous_row[x].nil? || previous_row[x] != row[x] }
      nxid = @common_helper.generate_nxid(nexpose_identifier_id, previous_row)
      info = @common_helper.get_field_info(matching_fields, previous_row)
      @log.log_message("Generated ticket with #{info}")

      host_vulns[nxid][:description] = @common_helper.print_description(description)
      previous_row = nil
      description = nil
      redo
    else
      description = @common_helper.update_description(description, row)
    end
  end

  unless host_vulns[nxid].nil? || host_vulns[nxid].empty?
    host_vulns[nxid][:description] = @common_helper.print_description(description)
  end

  host_vulns.each do |nxid, vuln_info|
    workorderid = initial_scan ? nil : find_ticket_in_database(nxid)
    if workorderid.nil? || workorderid.empty?
      @log.log_message("Creating new incident for assetid #{nxid}")
      tickets << { :action => :create, :nxid => nxid,
                   :description => create_ticket_request(vuln_info[:title], vuln_info[:description]) }
    else
      @log.log_message("Updating incident for assetid #{nxid}")
      tickets << { :action => :modify, :nxid => nxid, 
                   :workorderid => workorderid,
                   :description => modify_ticket_request(vuln_info[:description]) }
    end
  end
  tickets
end

#prepare_update_tickets(vulnerability_list, nexpose_identifier_id) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 296

def prepare_update_tickets(vulnerability_list, nexpose_identifier_id)
  @log.log_message('Preparing ticket updates...')
  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

#remove_tickets_from_database(tickets) ⇒ Object



52
53
54
55
56
57
58
59
60
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 52

def remove_tickets_from_database(tickets)
  db = open_database()
  tickets.each do |t|
    nxid = t[:nxid]
    @log.log_message("Removing workorder id from database for NXID <#{nxid}>")
    db.delete(nxid) unless db[nxid].nil?
  end
  db.close()
end

#submit_ticket(ticket) ⇒ Object



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
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 217

def submit_ticket(ticket)
  @log.log_message("Connecting to #{@rest_uri}.")
  uri = URI( @rest_uri )
  res = Net::HTTP::post_form(uri,
                             'OPERATION_NAME' => 'ADD_REQUEST',
                             'TECHNICIAN_KEY' => @api_key,
                             'INPUT_DATA' => ticket[:description])

  response = Nokogiri::XML.parse(res.read_body)
  begin
    status = response.xpath('//statuscode').text
    status_code = status.empty? ? -1 : Integer(status)
  
    if status_code != 200
      @log.log_message("Unable to create ticket #{ticket}, got response #{response.to_xml}")
      return
    end

    workorderid = Integer(response.xpath('//workorderid').text)
  rescue ArgumentError => ae
    @log.log_message("Failed to parse response from servicedesk #{response}")
    raise ae
  end

  @log.log_message( "created ticket #{workorderid}")
  add_ticket_to_database( workorderid, ticket[:nxid] )
end

#update_tickets(tickets) ⇒ Object



313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/nexpose_ticketing/helpers/servicedesk_helper.rb', line 313

def update_tickets(tickets)
  @log.log_message('Updating tickets')
  tickets.each do |ticket|
    if ticket[:action] == :create
      @log.log_message('Creating ticket')
      submit_ticket(ticket)
    else
      @log.log_message("Updating ticket #{ticket[:workorderid]}")
      modify_ticket(ticket)
    end
  end
end