nexpose 0.0.98 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,21 @@
1
- module Nexpose
2
- class APIError < ::RuntimeError
3
- attr_accessor :req, :reason
4
-
5
- def initialize(req, reason = '')
6
- @req = req
7
- @reason = reason
8
- end
9
-
10
- def to_s
11
- "NexposeAPI: #{@reason}"
12
- end
13
- end
14
-
15
- class AuthenticationFailed < APIError
16
- def initialize(req)
17
- @req = req
18
- @reason = "Login Failed"
19
- end
20
- end
21
- end
1
+ module Nexpose
2
+ class APIError < ::RuntimeError
3
+ attr_accessor :req, :reason
4
+
5
+ def initialize(req, reason = '')
6
+ @req = req
7
+ @reason = reason
8
+ end
9
+
10
+ def to_s
11
+ "NexposeAPI: #{@reason}"
12
+ end
13
+ end
14
+
15
+ class AuthenticationFailed < APIError
16
+ def initialize(req)
17
+ @req = req
18
+ @reason = "Login Failed"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,83 @@
1
+ # General management and diagnostic functions.
2
+ module Nexpose
3
+ module NexposeAPI
4
+ include XMLUtils
5
+
6
+ # Execute an arbitrary console command that is supplied as text via the
7
+ # supplied parameter. Console commands are documented in the
8
+ # administrator's guide. If you use a command that is not listed in the
9
+ # administrator's guide, the application will return the XMLResponse.
10
+ def console_command(cmd_string)
11
+ xml = make_xml('ConsoleCommandRequest', {})
12
+ cmd = REXML::Element.new('Command')
13
+ cmd.text = cmd_string
14
+ xml << cmd
15
+
16
+ r = execute(xml)
17
+ if (r.success)
18
+ res = ''
19
+ r.res.elements.each('//Output') do |out|
20
+ out.text.to_s
21
+ end
22
+ else
23
+ false
24
+ end
25
+ end
26
+
27
+ # Obtain system data, such as total RAM, free RAM, total disk space,
28
+ # free disk space, CPU speed, number of CPU cores, and other vital
29
+ # information.
30
+ def system_information
31
+ r = execute(make_xml('SystemInformationRequest', {}))
32
+
33
+ if (r.success)
34
+ res = {}
35
+ r.res.elements.each("//Statistic") do |stat|
36
+ res[stat.attributes['name'].to_s] = stat.text.to_s
37
+ end
38
+ res
39
+ else
40
+ false
41
+ end
42
+ end
43
+
44
+ # Induce the application to retrieve required updates and restart
45
+ # if necessary.
46
+ def start_update
47
+ execute(make_xml('StartUpdateRequest', {})).success
48
+ end
49
+
50
+ # Restart the application.
51
+ #
52
+ # There is no response to a RestartRequest. When the application
53
+ # shuts down as part of the restart process, it terminates any active
54
+ # connections. Therefore, the application cannot issue a response when it
55
+ # restarts.
56
+ def restart
57
+ execute(make_xml('RestartRequest', {})).success
58
+ end
59
+
60
+ # --
61
+ # TODO This is not yet implemented correctly.
62
+ #
63
+ # Output diagnostic information into log files, zip the files, and encrypt
64
+ # the archive with a PGP public key that is provided as a parameter for the
65
+ # API call. Then, either e-mail this archive to an address that is
66
+ # specified as an API parameter, or upload the archive using HTTP or HTTPS
67
+ # to a URL that is specified as an API parameter.
68
+ #
69
+ # If you do not specify a key, the SendLogRequest uses a default key.
70
+ #
71
+ # @param protocol should be one of: smtp, http, https.
72
+ # ++
73
+ def send_log(key_id, protocol, transport)
74
+ xml = make_xml('ConsoleCommandRequest', {'keyid' => key_id})
75
+ tpt = REXML::Element.new('Transport')
76
+ tpt.add_attribute('protocol', protocol)
77
+ tpt.text = transport
78
+ xml << tpt
79
+
80
+ # execute(xml)
81
+ end
82
+ end
83
+ end
@@ -1,122 +1,85 @@
1
- module Nexpose
2
- module NexposeAPI
3
- include XMLUtils
4
-
5
- def device_delete(param)
6
- r = execute(make_xml('DeviceDeleteRequest', {'device-id' => param}))
7
- r.success
8
- end
9
-
10
- def asset_group_delete(connection, id, debug = false)
11
- r = execute(make_xml('AssetGroupDeleteRequest', {'group-id' => param}))
12
- r.success
13
- end
14
-
15
- #-------------------------------------------------------------------------
16
- # Returns all asset group information
17
- #-------------------------------------------------------------------------
18
- def asset_groups_listing()
19
- r = execute(make_xml('AssetGroupListingRequest'))
20
-
21
- if r.success
22
- res = []
23
- r.res.elements.each('//AssetGroupSummary') do |group|
24
- res << {
25
- :asset_group_id => group.attributes['id'].to_i,
26
- :name => group.attributes['name'].to_s,
27
- :description => group.attributes['description'].to_s,
28
- :risk_score => group.attributes['riskscore'].to_f,
29
- }
30
- end
31
- res
32
- else
33
- false
34
- end
35
- end
36
-
37
- #-------------------------------------------------------------------------
38
- # Returns an asset group configuration information for a specific group ID
39
- #-------------------------------------------------------------------------
40
- def asset_group_config(group_id)
41
- r = execute(make_xml('AssetGroupConfigRequest', {'group-id' => group_id}))
42
-
43
- if r.success
44
- res = []
45
- r.res.elements.each('//Devices/device') do |device_info|
46
- res << {
47
- :device_id => device_info.attributes['id'].to_i,
48
- :site_id => device_info.attributes['site-id'].to_i,
49
- :address => device_info.attributes['address'].to_s,
50
- :riskfactor => device_info.attributes['riskfactor'].to_f,
51
- }
52
- end
53
- res
54
- else
55
- false
56
- end
57
- end
58
-
59
- #
60
- # Lists all the users for the NSC along with the user details.
61
- #
62
- def list_users
63
- r = execute(make_xml('UserListingRequest'))
64
- if r.success
65
- res = []
66
- r.res.elements.each('//UserSummary') do |user_summary|
67
- res << {
68
- :auth_source => user_summary.attributes['authSource'],
69
- :auth_module => user_summary.attributes['authModule'],
70
- :user_name => user_summary.attributes['userName'],
71
- :full_name => user_summary.attributes['fullName'],
72
- :email => user_summary.attributes['email'],
73
- :is_admin => user_summary.attributes['isAdmin'].to_s.chomp.eql?('1'),
74
- :is_disabled => user_summary.attributes['disabled'].to_s.chomp.eql?('1'),
75
- :site_count => user_summary.attributes['siteCount'],
76
- :group_count => user_summary.attributes['groupCount']
77
- }
78
- end
79
- res
80
- else
81
- false
82
- end
83
- end
84
-
85
-
86
- def console_command(cmd_string)
87
- xml = make_xml('ConsoleCommandRequest', {})
88
- cmd = REXML::Element.new('Command')
89
- cmd.text = cmd_string
90
- xml << cmd
91
-
92
- r = execute(xml)
93
-
94
- if (r.success)
95
- res = ""
96
- r.res.elements.each("//Output") do |out|
97
- res << out.text.to_s
98
- end
99
-
100
- res
101
- else
102
- false
103
- end
104
- end
105
-
106
- def system_information
107
- r = execute(make_xml('SystemInformationRequest', {}))
108
-
109
- if (r.success)
110
- res = {}
111
- r.res.elements.each("//Statistic") do |stat|
112
- res[stat.attributes['name'].to_s] = stat.text.to_s
113
- end
114
-
115
- res
116
- else
117
- false
118
- end
119
- end
120
-
121
- end
122
- end
1
+ module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ def device_delete(param)
6
+ r = execute(make_xml('DeviceDeleteRequest', {'device-id' => param}))
7
+ r.success
8
+ end
9
+
10
+ def asset_group_delete(connection, id, debug = false)
11
+ r = execute(make_xml('AssetGroupDeleteRequest', {'group-id' => param}))
12
+ r.success
13
+ end
14
+
15
+ #-------------------------------------------------------------------------
16
+ # Returns all asset group information
17
+ #-------------------------------------------------------------------------
18
+ def asset_groups_listing()
19
+ r = execute(make_xml('AssetGroupListingRequest'))
20
+
21
+ if r.success
22
+ res = []
23
+ r.res.elements.each('//AssetGroupSummary') do |group|
24
+ res << {
25
+ :asset_group_id => group.attributes['id'].to_i,
26
+ :name => group.attributes['name'].to_s,
27
+ :description => group.attributes['description'].to_s,
28
+ :risk_score => group.attributes['riskscore'].to_f,
29
+ }
30
+ end
31
+ res
32
+ else
33
+ false
34
+ end
35
+ end
36
+
37
+ #-------------------------------------------------------------------------
38
+ # Returns an asset group configuration information for a specific group ID
39
+ #-------------------------------------------------------------------------
40
+ def asset_group_config(group_id)
41
+ r = execute(make_xml('AssetGroupConfigRequest', {'group-id' => group_id}))
42
+
43
+ if r.success
44
+ res = []
45
+ r.res.elements.each('//Devices/device') do |device_info|
46
+ res << {
47
+ :device_id => device_info.attributes['id'].to_i,
48
+ :site_id => device_info.attributes['site-id'].to_i,
49
+ :address => device_info.attributes['address'].to_s,
50
+ :riskfactor => device_info.attributes['riskfactor'].to_f,
51
+ }
52
+ end
53
+ res
54
+ else
55
+ false
56
+ end
57
+ end
58
+
59
+ #
60
+ # Lists all the users for the NSC along with the user details.
61
+ #
62
+ def list_users
63
+ r = execute(make_xml('UserListingRequest'))
64
+ if r.success
65
+ res = []
66
+ r.res.elements.each('//UserSummary') do |user_summary|
67
+ res << {
68
+ :auth_source => user_summary.attributes['authSource'],
69
+ :auth_module => user_summary.attributes['authModule'],
70
+ :user_name => user_summary.attributes['userName'],
71
+ :full_name => user_summary.attributes['fullName'],
72
+ :email => user_summary.attributes['email'],
73
+ :is_admin => user_summary.attributes['isAdmin'].to_s.chomp.eql?('1'),
74
+ :is_disabled => user_summary.attributes['disabled'].to_s.chomp.eql?('1'),
75
+ :site_count => user_summary.attributes['siteCount'],
76
+ :group_count => user_summary.attributes['groupCount']
77
+ }
78
+ end
79
+ res
80
+ else
81
+ false
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,603 +1,783 @@
1
- # -*- coding: utf-8 -*-
2
- module Nexpose
3
- module NexposeAPI
4
- include XMLUtils
5
-
6
- #
7
- #
8
- #
9
- def report_generate(param)
10
- r = execute(make_xml('ReportGenerateRequest', {'report-id' => param}))
11
- r.success
12
- end
13
-
14
- #
15
- #
16
- #
17
- def report_last(param)
18
- r = execute(make_xml('ReportHistoryRequest', {'reportcfg-id' => param}))
19
- res = nil
20
- if (r.success)
21
- stk = []
22
- r.res.elements.each("//ReportSummary") do |rep|
23
- stk << [rep.attributes['id'].to_i, rep.attributes['report-URI']]
24
- end
25
- if (stk.length > 0)
26
- stk.sort! { |a, b| b[0] <=> a[0] }
27
- res = stk[0][1]
28
- end
29
- end
30
- res
31
- end
32
-
33
- #
34
- #
35
- #
36
- def report_history(param)
37
- execute(make_xml('ReportHistoryRequest', {'reportcfg-id' => param}))
38
- end
39
-
40
- #
41
- #
42
- #
43
- def report_config_delete(param)
44
- r = execute(make_xml('ReportDeleteRequest', {'reportcfg-id' => param}))
45
- r.success
46
- end
47
-
48
- #
49
- #
50
- #
51
- def report_delete(param)
52
- r = execute(make_xml('ReportDeleteRequest', {'report-id' => param}))
53
- r.success
54
- end
55
-
56
- #
57
- #
58
- #
59
- def report_template_listing
60
- r = execute(make_xml('ReportTemplateListingRequest', {}))
61
-
62
- if (r.success)
63
- res = []
64
- r.res.elements.each("//ReportTemplateSummary") do |template|
65
- desc = ''
66
- template.elements.each("//description") do |ent|
67
- desc = ent.text
68
- end
69
-
70
- res << {
71
- :template_id => template.attributes['id'].to_s,
72
- :name => template.attributes['name'].to_s,
73
- :description => desc.to_s
74
- }
75
- end
76
- res
77
- else
78
- false
79
- end
80
- end
81
- end
82
-
83
- # === Description
84
- # Object that represents the summary of a Report Configuration.
85
- #
86
- class ReportConfigSummary
87
- # The Report Configuration ID
88
- attr_reader :id
89
- # A unique name for the Report
90
- attr_reader :name
91
- # The report format
92
- attr_reader :format
93
- # The date of the last report generation
94
- attr_reader :last_generated_on
95
- # Relative URI of the last generated report
96
- attr_reader :last_generated_uri
97
-
98
- # Constructor
99
- # ReportConfigSummary(id, name, format, last_generated_on, last_generated_uri)
100
- def initialize(id, name, format, last_generated_on, last_generated_uri)
101
-
102
- @id = id
103
- @name = name
104
- @format = format
105
- @last_generated_on = last_generated_on
106
- @last_generated_uri = last_generated_uri
107
-
108
- end
109
- end
110
-
111
- # === Description
112
- # Object that represents the schedule on which to automatically generate new reports.
113
- class ReportHistory
114
-
115
- # true if an error condition exists; false otherwise
116
- attr_reader :error
117
- # Error message string
118
- attr_reader :error_msg
119
- # The last XML request sent by this object
120
- attr_reader :request_xml
121
- # The last XML response received by this object
122
- attr_reader :response_xml
123
- # The NSC Connection associated with this object
124
- attr_reader :connection
125
- # The report definition (report config) ID
126
- # Report definition ID
127
- attr_reader :config_id
128
- # Array (ReportSummary*)
129
- attr_reader :report_summaries
130
-
131
-
132
- def initialize(connection, config_id)
133
-
134
- @error = false
135
- @connection = connection
136
- @config_id = config_id
137
- @report_summaries = []
138
-
139
- reportHistory_request = APIRequest.new('<ReportHistoryRequest session-id="' + "#{connection.session_id}" + '" reportcfg-id="' + "#{@config_id}" + '"/>', @connection.url)
140
- reportHistory_request.execute()
141
- @response_xml = reportHistory_request.response_xml
142
- @request_xml = reportHistory_request.request_xml
143
-
144
- end
145
-
146
- def xml_parse(response)
147
- response = REXML::Document.new(response.to_s)
148
- status = response.root.attributes['success']
149
- if (status == '1')
150
- response.elements.each('ReportHistoryResponse/ReportSummary') do |r|
151
- @report_summaries.push(ReportSummary.new(r.attributes["id"], r.attributes["cfg-id"], r.attributes["status"], r.attributes["generated-on"], r.attributes['report-uri']))
152
- end
153
- else
154
- @error = true
155
- @error_msg = 'Error ReportHistoryReponse'
156
- end
157
- end
158
-
159
- end
160
-
161
- # === Description
162
- # Object that represents the summary of a single report.
163
- class ReportSummary
164
-
165
- # The Report ID
166
- attr_reader :id
167
- # The Report Configuration ID
168
- attr_reader :cfg_id
169
- # The status of this report
170
- # available | generating | failed
171
- attr_reader :status
172
- # The date on which this report was generated
173
- attr_reader :generated_on
174
- # The relative URI of the report
175
- attr_reader :report_uri
176
-
177
- def initialize(id, cfg_id, status, generated_on, report_uri)
178
-
179
- @id = id
180
- @cfg_id = cfg_id
181
- @status = status
182
- @generated_on = generated_on
183
- @report_uri = report_uri
184
-
185
- end
186
-
187
- end
188
-
189
- # === Description
190
- #
191
- class ReportAdHoc
192
- include XMLUtils
193
-
194
- attr_reader :error
195
- attr_reader :error_msg
196
- attr_reader :connection
197
- # Report Template ID strong e.g. full-audit
198
- attr_reader :template_id
199
- # pdf|html|xml|text|csv|raw-xml
200
- attr_reader :format
201
- # Array of (ReportFilter)*
202
- attr_reader :filters
203
- attr_reader :request_xml
204
- attr_reader :response_xml
205
- attr_reader :report_decoded
206
-
207
-
208
- def initialize(connection, template_id = 'full-audit', format = 'raw-xml')
209
-
210
- @error = false
211
- @connection = connection
212
- @filters = []
213
- @template_id = template_id
214
- @format = format
215
-
216
- end
217
-
218
- def addFilter(filter_type, id)
219
-
220
- # filter_type can be site|group|device|scan
221
- # id is the ID number. For scan, you can use 'last' for the most recently run scan
222
- filter = ReportFilter.new(filter_type, id)
223
- filters.push(filter)
224
-
225
- end
226
-
227
- def generate()
228
- request_xml = '<ReportAdhocGenerateRequest session-id="' + @connection.session_id + '">'
229
- request_xml += '<AdhocReportConfig template-id="' + @template_id + '" format="' + @format + '">'
230
- request_xml += '<Filters>'
231
- @filters.each do |f|
232
- request_xml += '<filter type="' + f.type + '" id="'+ f.id.to_s + '"/>'
233
- end
234
- request_xml += '</Filters>'
235
- request_xml += '</AdhocReportConfig>'
236
- request_xml += '</ReportAdhocGenerateRequest>'
237
-
238
- ad_hoc_request = APIRequest.new(request_xml, @connection.url)
239
- ad_hoc_request.execute()
240
-
241
- content_type_response = ad_hoc_request.raw_response.header['Content-Type']
242
- if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
243
- # NeXpose sends an incorrect boundary format which breaks parsing
244
- # Eg: boundary=XXX; charset=XXX
245
- # Fix by removing everything from the last semi-colon onward
246
- last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
247
- content_type_response = content_type_response[0, last_semi_colon_index]
248
-
249
- data = "Content-Type: " + content_type_response + "\r\n\r\n" + ad_hoc_request.raw_response_data
250
- doc = Rex::MIME::Message.new data
251
- doc.parts.each do |part|
252
- if /.*base64.*/ =~ part.header.to_s
253
- if (@format == "text") or (@format == "pdf") or (@format == "csv")
254
- return part.content.unpack("m*")[0]
255
- else
256
- return parse_xml(part.content.unpack("m*")[0])
257
- end
258
- end
259
- end
260
- end
261
- end
262
-
263
- end
264
-
265
- # === Description
266
- # Object that represents the configuration of a report definition.
267
- #
268
- class ReportConfig
269
-
270
- # true if an error condition exists; false otherwise
271
- attr_reader :error
272
- # Error message string
273
- attr_reader :error_msg
274
- # The last XML request sent by this object
275
- attr_reader :request_xml
276
- # The last XML response received by this object
277
- attr_reader :response_xml
278
- # The NSC Connection associated with this object
279
- attr_reader :connection
280
- # The ID for this report definition
281
- attr_reader :config_id
282
- # A unique name for this report definition
283
- attr_reader :name
284
- # The template ID used for this report definition
285
- attr_reader :template_id
286
- # html, db, txt, xml, raw-xml, csv, pdf
287
- attr_reader :format
288
- # XXX new
289
- attr_reader :timezone
290
- # XXX new
291
- attr_reader :owner
292
- # Array of (ReportFilter)* - The Sites, Asset Groups, or Devices to run the report against
293
- attr_reader :filters
294
- # Automatically generate a new report at the conclusion of a scan
295
- # 1 or 0
296
- attr_reader :generate_after_scan
297
- # Schedule to generate reports
298
- # ReportSchedule Object
299
- attr_reader :schedule
300
- # Store the reports on the server
301
- # 1 or 0
302
- attr_reader :storeOnServer
303
- # Location to store the report on the server
304
- attr_reader :store_location
305
- # Form to send the report via email
306
- # "file", "zip", "url", or NULL (don’t send email)
307
- attr_reader :email_As
308
- # Send the Email to all Authorized Users
309
- # boolean - Send the Email to all Authorized Users
310
- attr_reader :email_to_all
311
- # Array containing the email addresses of the recipients
312
- attr_reader :email_recipients
313
- # IP Address or Hostname of SMTP Relay Server
314
- attr_reader :smtp_relay_server
315
- # Sets the FROM field of the Email
316
- attr_reader :sender
317
- # TODO
318
- attr_reader :db_export
319
- # TODO
320
- attr_reader :csv_export
321
- # TODO
322
- attr_reader :xml_export
323
-
324
-
325
- def initialize(connection, config_id = -1)
326
-
327
- @error = false
328
- @connection = connection
329
- @config_id = config_id
330
- @xml_tag_stack = []
331
- @filters = []
332
- @email_recipients = []
333
- @name = "New Report " + rand(999999999).to_s
334
-
335
- r = @connection.execute('<ReportConfigRequest session-id="' + @connection.session_id.to_s + '" reportcfg-id="' + @config_id.to_s + '"/>')
336
- if (r.success)
337
- r.res.elements.each('ReportConfigResponse/ReportConfig') do |r|
338
- @name = r.attributes['name']
339
- @format = r.attributes['format']
340
- @timezone = r.attributes['timezone']
341
- @id = r.attributes['id']
342
- @template_id = r.attributes['template-id']
343
- @owner = r.attributes['owner']
344
- end
345
- else
346
- @error = true
347
- @error_msg = 'Error ReportHistoryReponse'
348
- end
349
- end
350
-
351
- # === Description
352
- # Generate a new report on this report definition. Returns the new report ID.
353
- def generateReport(debug = false)
354
- return generateReport(@connection, @config_id, debug)
355
- end
356
-
357
- # === Description
358
- # Save the report definition to the NSC.
359
- # Returns the config-id.
360
- def saveReport()
361
- r = @connection.execute('<ReportSaveRequest session-id="' + @connection.session_id.to_s + '">' + getXML().to_s + ' </ReportSaveRequest>')
362
- if (r.success)
363
- @config_id = r.attributes['reportcfg-id']
364
- return true
365
- end
366
- return false
367
- end
368
-
369
- # === Description
370
- # Adds a new filter to the report config
371
- def addFilter(filter_type, id)
372
- filter = ReportFilter.new(filter_type, id)
373
- @filters.push(filter)
374
- end
375
-
376
- # === Description
377
- # Adds a new email recipient
378
- def addEmailRecipient(recipient)
379
- @email_recipients.push(recipient)
380
- end
381
-
382
- # === Description
383
- # Sets the schedule for this report config
384
- def setSchedule(schedule)
385
- @schedule = schedule
386
- end
387
-
388
- def getXML()
389
-
390
- xml = '<ReportConfig id="' + @config_id.to_s + '" name="' + @name.to_s + '" template-id="' + @template_id.to_s + '" format="' + @format.to_s + '">'
391
-
392
- xml += ' <Filters>'
393
-
394
- @filters.each do |f|
395
- xml += ' <filter type="' + f.type.to_s + '" id="' + f.id.to_s + '"/>'
396
- end
397
-
398
- xml += ' </Filters>'
399
-
400
- xml += ' <Generate after-scan="' + @generate_after_scan.to_s + '">'
401
-
402
- if (@schedule)
403
- xml += ' <Schedule type="' + @schedule.type.to_s + '" interval="' + @schedule.interval.to_s + '" start="' + @schedule.start.to_s + '"/>'
404
- end
405
-
406
- xml += ' </Generate>'
407
-
408
- xml += ' <Delivery>'
409
-
410
- xml += ' <Storage storeOnServer="' + @storeOnServer.to_s + '">'
411
-
412
- if (@store_location and @store_location.length > 0)
413
- xml += ' <location>' + @store_location.to_s + '</location>'
414
- end
415
-
416
- xml += ' </Storage>'
417
-
418
-
419
- xml += ' </Delivery>'
420
-
421
- xml += ' </ReportConfig>'
422
-
423
- return xml
424
- end
425
-
426
- def set_name(name)
427
- @name = name
428
- end
429
-
430
- def set_template_id(template_id)
431
- @template_id = template_id
432
- end
433
-
434
- def set_format(format)
435
- @format = format
436
- end
437
-
438
- def set_email_As(email_As)
439
- @email_As = email_As
440
- end
441
-
442
- def set_storeOnServer(storeOnServer)
443
- @storeOnServer = storeOnServer
444
- end
445
-
446
- def set_smtp_relay_server(smtp_relay_server)
447
- @smtp_relay_server = smtp_relay_server
448
- end
449
-
450
- def set_sender(sender)
451
- @sender = sender
452
- end
453
-
454
- def set_generate_after_scan(generate_after_scan)
455
- @generate_after_scan = generate_after_scan
456
- end
457
- end
458
-
459
- # === Description
460
- # Object that represents a report filter which determines which sites, asset
461
- # groups, and/or devices that a report is run against. gtypes are
462
- # "SiteFilter", "AssetGroupFilter", "DeviceFilter", or "ScanFilter". gid is
463
- # the site-id, assetgroup-id, or devce-id. ScanFilter, if used, specifies
464
- # a specifies a specific scan to use as the data source for the report. The gid
465
- # can be a specific scan-id or "first" for the first run scan, or “last” for
466
- # the last run scan.
467
- #
468
- class ReportFilter
469
-
470
- attr_reader :type
471
- attr_reader :id
472
-
473
- def initialize(type, id)
474
-
475
- @type = type
476
- @id = id
477
-
478
- end
479
-
480
- end
481
-
482
-
483
- # === Description
484
- # Object that represents the schedule on which to automatically generate new reports.
485
- #
486
- class ReportSchedule
487
-
488
- # The type of schedule
489
- # (daily, hourly, monthly, weekly)
490
- attr_reader :type
491
- # The frequency with which to run the scan
492
- attr_reader :interval
493
- # The earliest date to generate the report
494
- attr_reader :start
495
-
496
- def initialize(type, interval, start)
497
-
498
- @type = type
499
- @interval = interval
500
- @start = start
501
-
502
- end
503
-
504
-
505
- end
506
-
507
- class ReportTemplateListing
508
-
509
- attr_reader :error_msg
510
- attr_reader :error
511
- attr_reader :request_xml
512
- attr_reader :response_xml
513
- attr_reader :connection
514
- attr_reader :xml_tag_stack
515
- attr_reader :report_template_summaries #; //Array (ReportTemplateSummary*)
516
-
517
-
518
- def initialize(connection)
519
-
520
- @error = nil
521
- @connection = connection
522
- @report_template_summaries = []
523
-
524
- r = @connection.execute('<ReportTemplateListingRequest session-id="' + connection.session_id.to_s + '"/>')
525
- if (r.success)
526
- r.res.elements.each('ReportTemplateListingResponse/ReportTemplateSummary') do |r|
527
- @report_template_summaries.push(ReportTemplateSummary.new(r.attributes['id'], r.attributes['name'], r.attributes['description']))
528
- end
529
- else
530
- @error = true
531
- @error_msg = 'ReportTemplateListingRequest Parse Error'
532
- end
533
-
534
- end
535
-
536
- end
537
-
538
- class ReportListing
539
-
540
- attr_reader :error_msg
541
- attr_reader :error
542
- attr_reader :request_xml
543
- attr_reader :response_xml
544
- attr_reader :connection
545
- attr_reader :xml_tag_stack
546
- attr_reader :report_summaries #; //Array (ReportSummary*)
547
-
548
- def initialize(connection)
549
-
550
- @error = nil
551
- @connetion = connection
552
- @report_summaries = []
553
-
554
- r = @connetion.execute('<ReportListingRequest session-id="' + connection.session_id.to_s + '"/>')
555
- if (r.success)
556
- r.res.elements.each('ReportListingResponse/ReportConfigSummary') do |r|
557
- @report_summaries.push(ReportSummary.new(r.attributes['template-id'], r.attributes['cfg-id'], r.attributes['status'], r.attributes['generated-on'], r.attributes['report-URI']))
558
- end
559
- else
560
- @error = true
561
- @error_msg = 'ReportListingRequest Parse Error'
562
- end
563
- end
564
- end
565
-
566
-
567
- class ReportTemplateSummary
568
-
569
- attr_reader :id
570
- attr_reader :name
571
- attr_reader :description
572
-
573
- def initialize(id, name, description)
574
-
575
- @id = id
576
- @name = name
577
- @description = description
578
-
579
- end
580
-
581
- end
582
-
583
-
584
- class ReportSection
585
-
586
- attr_reader :name
587
- attr_reader :properties
588
-
589
- def initialize(name)
590
-
591
- @properties = []
592
- @name = name
593
- end
594
-
595
-
596
- def addProperty(name, value)
597
-
598
- @properties[name.to_s] = value
599
- end
600
-
601
- end
602
-
603
- end
1
+ module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ # Generate a new report using the specified report definition.
6
+ def generate_report(report_id, wait = false)
7
+ xml = make_xml('ReportGenerateRequest', {'report-id' => report_id})
8
+ response = execute(xml)
9
+ summary = nil
10
+ if response.success
11
+ response.res.elements.each('//ReportSummary') do |summary|
12
+ summary = ReportSummary.parse(summary)
13
+ # If not waiting or the report is finished, return now.
14
+ return summary unless wait and summary.status == 'Started'
15
+ end
16
+ end
17
+ so_far = 0
18
+ while wait
19
+ summary = last_report(report_id)
20
+ return summary unless summary.status == 'Started'
21
+ sleep 5
22
+ so_far += 5
23
+ if so_far % 60 == 0
24
+ puts "Still waiting. Current status: #{summary.status}"
25
+ end
26
+ end
27
+ nil
28
+ end
29
+
30
+ # Provide a history of all reports generated with the specified report
31
+ # definition.
32
+ def report_history(report_config_id)
33
+ xml = make_xml('ReportHistoryRequest', {'reportcfg-id' => report_config_id})
34
+ ReportSummary.parse_all(execute(xml))
35
+ end
36
+
37
+ # Get the details of the last report generated with the specified report id.
38
+ def last_report(report_config_id)
39
+ history = report_history(report_config_id)
40
+ history.sort { |a, b| b.generated_on <=> a.generated_on }.first
41
+ end
42
+
43
+ # Delete a previously generated report definition.
44
+ # Also deletes any reports generated from that configuration.
45
+ def delete_report_config(report_config_id)
46
+ xml = make_xml('ReportDeleteRequest', {'reportcfg-id' => report_config_id})
47
+ execute(xml).success
48
+ end
49
+
50
+ # Delete a previously generated report.
51
+ def delete_report(report_id)
52
+ xml = make_xml('ReportDeleteRequest', {'report-id' => report_id})
53
+ execute(xml).success
54
+ end
55
+
56
+ # Provide a list of all report templates the user can access on the
57
+ # Security Console.
58
+ def report_template_listing
59
+ r = execute(make_xml('ReportTemplateListingRequest', {}))
60
+ templates = []
61
+ if (r.success)
62
+ r.res.elements.each('//ReportTemplateSummary') do |template|
63
+ templates << ReportTemplateSummary.parse(template)
64
+ end
65
+ end
66
+ templates
67
+ end
68
+
69
+ # Retrieve the configuration for a report template.
70
+ def get_report_template(template_id)
71
+ xml = make_xml('ReportTemplateConfigRequest', {'template-id' => template_id})
72
+ ReportTemplate.parse(execute(xml))
73
+ end
74
+
75
+ # Provide a listing of all report definitions the user can access on the
76
+ # Security Console.
77
+ def report_listing
78
+ r = execute(make_xml('ReportListingRequest', {}))
79
+ reports = []
80
+ if (r.success)
81
+ r.res.elements.each('//ReportConfigSummary') do |report|
82
+ reports << ReportConfigSummary.parse(report)
83
+ end
84
+ end
85
+ reports
86
+ end
87
+
88
+ # Retrieve the configuration for a report definition.
89
+ def get_report_config(report_config_id)
90
+ xml = make_xml('ReportConfigRequest', {'reportcfg-id' => report_config_id})
91
+ ReportConfig.parse(execute(xml))
92
+ end
93
+ end
94
+
95
+ # Data object for report configuration information.
96
+ # Not meant for use in creating new configurations.
97
+ class ReportConfigSummary
98
+ # The report definition (config) ID.
99
+ attr_reader :config_id
100
+ # The ID of the report template.
101
+ attr_reader :template_id
102
+ # The current status of the report.
103
+ # One of: Started|Generated|Failed|Aborted|Unknown
104
+ attr_reader :status
105
+ # The date and time the report was generated, in ISO 8601 format.
106
+ attr_reader :generated_on
107
+ # The URL to use to access the report (not set for database exports).
108
+ attr_reader :uri
109
+ # The visibility (scope) of the report definition.
110
+ # One of: (global|silo).
111
+ attr_reader :scope
112
+
113
+ def initialize(config_id, template_id, status, generated_on, uri, scope)
114
+ @config_id = config_id
115
+ @template_id = template_id
116
+ @status = status
117
+ @generated_on = generated_on
118
+ @uri = uri
119
+ @scope = scope
120
+ end
121
+
122
+ def self.parse(xml)
123
+ ReportConfigSummary.new(xml.attributes['cfg-id'],
124
+ xml.attributes['template-id'],
125
+ xml.attributes['status'],
126
+ xml.attributes['generated-on'],
127
+ xml.attributes['report-URI'],
128
+ xml.attributes['scope'])
129
+ end
130
+ end
131
+
132
+ # Summary of a single report.
133
+ class ReportSummary
134
+ # The id of the generated report.
135
+ attr_reader :id
136
+ # The report definition (configuration) ID.
137
+ attr_reader :config_id
138
+ # The current status of the report.
139
+ # One of: Started|Generated|Failed|Aborted|Unknown
140
+ attr_reader :status
141
+ # The date and time the report was generated, in ISO 8601 format.
142
+ attr_reader :generated_on
143
+ # The relative URI to use to access the report.
144
+ attr_reader :uri
145
+
146
+ def initialize(id, config_id, status, generated_on, uri)
147
+ @id = id
148
+ @config_id = config_id
149
+ @status = status
150
+ @generated_on = generated_on
151
+ @uri = uri
152
+ end
153
+
154
+ # Delete this report.
155
+ def delete(connection)
156
+ connection.delete_report(@id)
157
+ end
158
+
159
+ def self.parse(xml)
160
+ ReportSummary.new(xml.attributes['id'], xml.attributes['cfg-id'], xml.attributes['status'], xml.attributes['generated-on'], xml.attributes['report-URI'])
161
+ end
162
+
163
+ def self.parse_all(response)
164
+ summaries = []
165
+ if (response.success)
166
+ response.res.elements.each('//ReportSummary') do |summary|
167
+ summaries << ReportSummary.parse(summary)
168
+ end
169
+ end
170
+ summaries
171
+ end
172
+ end
173
+
174
+ # Definition object for an adhoc report configuration.
175
+ #
176
+ # NOTE: Only text, pdf, and csv currently work reliably.
177
+ class AdhocReportConfig
178
+ # The ID of the report template used.
179
+ attr_accessor :template_id
180
+ # Format. One of: pdf|html|rtf|xml|text|csv|db|raw-xml|raw-xml-v2|ns-xml|qualys-xml
181
+ attr_accessor :format
182
+
183
+ # Array of filters associated with this report.
184
+ attr_accessor :filters
185
+ # Baseline comparison highlights the changes between two scans, including
186
+ # newly discovered assets, services and vulnerabilities, assets and services
187
+ # that are no longer available and vulnerabilities that were mitigated or
188
+ # fixed. The current scan results can be compared against the results of the
189
+ # first scan, the most recent (previous) scan, or the scan results from a
190
+ # particular date.
191
+ attr_accessor :baseline
192
+
193
+ def initialize(template_id, format, site_id = nil)
194
+ @template_id = template_id
195
+ @format = format
196
+
197
+ @filters = []
198
+ @filters << Filter.new('site', site_id) if site_id
199
+ end
200
+
201
+ # Add a new filter to this report configuration.
202
+ def add_filter(type, id)
203
+ filters << Filter.new(type, id)
204
+ end
205
+
206
+ def to_xml
207
+ xml = %Q{<AdhocReportConfig format='#{@format}' template-id='#{@template_id}'>}
208
+
209
+ xml << '<Filters>'
210
+ @filters.each { |filter| xml << filter.to_xml }
211
+ xml << '</Filters>'
212
+
213
+ xml << %Q{<Baseline compareTo='#{@baseline}' />} if @baseline
214
+
215
+ xml << '</AdhocReportConfig>'
216
+ end
217
+
218
+ include XMLUtils
219
+
220
+ # Generate a report once using a simple configuration, and send it back
221
+ # in a multi-part mime response.
222
+ def generate(connection)
223
+ xml = %Q{<ReportAdhocGenerateRequest session-id='#{connection.session_id}'>}
224
+ xml << to_xml
225
+ xml << '</ReportAdhocGenerateRequest>'
226
+ response = connection.execute(xml)
227
+ if response.success
228
+ content_type_response = response.raw_response.header['Content-Type']
229
+ if content_type_response =~ /multipart\/mixed;\s*boundary=([^\s]+)/
230
+ # Nexpose sends an incorrect boundary format which breaks parsing
231
+ # e.g., boundary=XXX; charset=XXX
232
+ # Fix by removing everything from the last semi-colon onward.
233
+ last_semi_colon_index = content_type_response.index(/;/, content_type_response.index(/boundary/))
234
+ content_type_response = content_type_response[0, last_semi_colon_index]
235
+
236
+ data = 'Content-Type: ' + content_type_response + "\r\n\r\n" + response.raw_response_data
237
+ doc = Rex::MIME::Message.new(data)
238
+ doc.parts.each do |part|
239
+ if /.*base64.*/ =~ part.header.to_s
240
+ if (@format == 'text') or (@format == 'pdf') or (@format == 'csv')
241
+ return part.content.unpack('m*')[0]
242
+ else
243
+ # FIXME This isn't working.
244
+ return parse_xml(part.content.unpack("m*")[0])
245
+ end
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ # Definition object for a report configuration.
254
+ class ReportConfig < AdhocReportConfig
255
+ # The ID of the report definition (config).
256
+ # Use -1 to create a new definition.
257
+ attr_accessor :id
258
+ # The unique name assigned to the report definition.
259
+ attr_accessor :name
260
+ attr_accessor :owner
261
+ attr_accessor :time_zone
262
+
263
+ # Description associated with this report.
264
+ attr_accessor :description
265
+ # Array of user IDs which have access to resulting reports.
266
+ attr_accessor :users
267
+ # Configuration of when a report is generated.
268
+ attr_accessor :generate
269
+ # Report delivery configuration.
270
+ attr_accessor :delivery
271
+ # Database export configuration.
272
+ attr_accessor :db_export
273
+
274
+ # Construct a basic ReportConfig object.
275
+ def initialize(name, template_id, format, id = -1, owner = nil, time_zone = nil)
276
+ @name = name
277
+ @template_id = template_id
278
+ @format = format
279
+ @id = id
280
+ @owner = owner
281
+ @time_zone = time_zone
282
+
283
+ @filters = []
284
+ @users = []
285
+ end
286
+
287
+ # Retrieve the configuration for an existing report definition.
288
+ def self.get(connection, report_config_id)
289
+ connection.get_report_config(report_config_id)
290
+ end
291
+
292
+ # Build and save a report configuration against the specified site using
293
+ # the supplied type and format.
294
+ #
295
+ # Returns the new configuration.
296
+ def self.build(connection, site_id, site_name, type, format, generate_now = false)
297
+ name = %Q{#{site_name} #{type} report in #{format}}
298
+ config = ReportConfig.new(name, type, format)
299
+ config.generate = Generate.new(true, false)
300
+ config.filters << Filter.new('site', site_id)
301
+ config.save(connection, generate_now)
302
+ config
303
+ end
304
+
305
+ # Save the configuration of this report definition.
306
+ def save(connection, generate_now = false)
307
+ xml = %Q{<ReportSaveRequest session-id='#{connection.session_id}' generate-now='#{generate_now ? 1 : 0}'>}
308
+ xml << to_xml
309
+ xml << '</ReportSaveRequest>'
310
+ response = connection.execute(xml)
311
+ if response.success
312
+ @id = response.attributes['reportcfg-id']
313
+ end
314
+ end
315
+
316
+ # Generate a new report using this report definition.
317
+ def generate(connection, wait = false)
318
+ connection.generate_report(@id, wait)
319
+ end
320
+
321
+ # Delete this report definition from the Security Console.
322
+ # Deletion will also remove all reports previously generated from the
323
+ # configuration.
324
+ def delete(connection)
325
+ connection.delete_report_config(@id)
326
+ end
327
+
328
+ def to_xml
329
+ xml = %Q{<ReportConfig format='#{@format}' id='#{@id}' name='#{@name}' owner='#{@owner}' template-id='#{@template_id}' timezone='#{@time_zone}'>}
330
+ xml << %Q{<description>#{@description}</description>} if @description
331
+
332
+ xml << '<Filters>'
333
+ @filters.each { |filter| xml << filter.to_xml }
334
+ xml << '</Filters>'
335
+
336
+ xml << '<Users>'
337
+ @users.each { |user| xml << %Q{<user id='#{user}' />} }
338
+ xml << '</Users>'
339
+
340
+ xml << %Q{<Baseline compareTo='#{@baseline}' />} if @baseline
341
+ xml << @generate.to_xml if @generate
342
+ xml << @delivery.to_xml if @delivery
343
+ xml << @db_export.to_xml if @db_export
344
+
345
+ xml << '</ReportConfig>'
346
+ end
347
+
348
+ def self.parse(xml)
349
+ xml.res.elements.each('//ReportConfig') do |cfg|
350
+ config = ReportConfig.new(cfg.attributes['name'],
351
+ cfg.attributes['template-id'],
352
+ cfg.attributes['format'],
353
+ cfg.attributes['id'],
354
+ cfg.attributes['owner'],
355
+ cfg.attributes['timezone'])
356
+
357
+ cfg.elements.each('//description') do |desc|
358
+ config.description = desc.text
359
+ end
360
+
361
+ config.filters = Filter.parse(xml)
362
+
363
+ cfg.elements.each('//user') do |user|
364
+ config.users << user.attributes['id'].to_i
365
+ end
366
+
367
+ cfg.elements.each('//Baseline') do |baseline|
368
+ config.baseline = baseline.attributes['compareTo']
369
+ end
370
+
371
+ config.generate = Generate.parse(cfg)
372
+ config.delivery = Delivery.parse(cfg)
373
+ config.db_export = DBExport.parse(cfg)
374
+
375
+ return config
376
+ end
377
+ nil
378
+ end
379
+ end
380
+
381
+ # Object that represents a report filter which determines which sites, asset
382
+ # groups, and/or devices that a report is run against.
383
+ #
384
+ # The configuration must include at least one of device (asset), site,
385
+ # group (asset group) or scan filter to define the scope of report.
386
+ # The vuln-status filter can be used only with raw report formats: csv
387
+ # or raw_xml. If the vuln-status filter is not included in the configuration,
388
+ # all the vulnerability test results (including invulnerable instances) are
389
+ # exported by default in csv and raw_xml reports.
390
+ class Filter
391
+ # The ID of the specific site, group, device, or scan.
392
+ # For scan, this can also be "last" for the most recently run scan.
393
+ # For vuln-status, the ID can have one of the following values:
394
+ # 1. vulnerable-exploited (The check was positive. An exploit verified the vulnerability.)
395
+ # 2. vulnerable-version (The check was positive. The version of the scanned service or software is associated with known vulnerabilities.)
396
+ # 3. potential (The check for a potential vulnerability was positive.)
397
+ # These values are supported for CSV and XML formats.
398
+ attr_reader :id
399
+ # One of: site|group|device|scan|vuln-categories|vuln-severity|vuln-status|cyberscope-component|cyberscope-bureau|cyberscope-enclave
400
+ attr_reader :type
401
+
402
+ def initialize(type, id)
403
+ @type = type
404
+ @id = id
405
+ end
406
+
407
+ def to_xml
408
+ %Q{<filter id='#{@id}' type='#{@type}' />}
409
+ end
410
+
411
+ def self.parse(xml)
412
+ filters = []
413
+ xml.res.elements.each('//Filters/filter') do |filter|
414
+ filters << Filter.new(filter.attributes['type'], filter.attributes['id'])
415
+ end
416
+ filters
417
+ end
418
+ end
419
+
420
+ # Data object associated with when a report is generated.
421
+ class Generate
422
+ # Will the report be generated after a scan completes (true),
423
+ # or is it ad-hoc/scheduled (false).
424
+ attr_accessor :after_scan
425
+ # Whether or not a scan is scheduled.
426
+ attr_accessor :scheduled
427
+ # Schedule associated with the report.
428
+ attr_accessor :schedule
429
+
430
+ def initialize(after_scan, scheduled, schedule = nil)
431
+ @after_scan = after_scan
432
+ @scheduled = scheduled
433
+ @schedule = schedule
434
+ end
435
+
436
+ def to_xml
437
+ xml = %Q{<Generate after-scan='#{@after_scan ? 1 : 0}' schedule='#{@scheduled ? 1 : 0}'>}
438
+ xml << @schedule.to_xml if @schedule
439
+ xml << '</Generate>'
440
+ end
441
+
442
+ def self.parse(xml)
443
+ xml.elements.each('//Generate') do |generate|
444
+ if generate.attributes['after-scan'] == '1'
445
+ return Generate.new(true, false)
446
+ else
447
+ if generate.attributes['schedule'] == '1'
448
+ schedule = Schedule.parse(xml)
449
+ return Generate.new(false, true, schedule)
450
+ end
451
+ return Generate.new(false, false)
452
+ end
453
+ end
454
+ nil
455
+ end
456
+ end
457
+
458
+ # Data object for configuration of where a report is stored or delivered.
459
+ class Delivery
460
+ # Whether to store report on server.
461
+ attr_accessor :store_on_server
462
+ # Directory location to store report in (for non-default storage).
463
+ attr_accessor :location
464
+ # E-mail configuration.
465
+ attr_accessor :email
466
+
467
+ def initialize(store_on_server, location = nil, email = nil)
468
+ @store_on_server = store_on_server
469
+ @location = location
470
+ @email = email
471
+ end
472
+
473
+ def to_xml
474
+ xml = '<Delivery>'
475
+ xml << %Q{<Storage storeOnServer='#{@store_on_server ? 1 : 0}'>}
476
+ xml << %Q{<location>#{@location}</location>} if @location
477
+ xml << '</Storage>'
478
+ xml << @email.to_xml if @email
479
+ xml << '</Delivery>'
480
+ end
481
+
482
+ def self.parse(xml)
483
+ xml.elements.each('//Delivery') do |delivery|
484
+ on_server = false
485
+ location = nil
486
+ xml.elements.each('//Storage') do |storage|
487
+ on_server = true if storage.attributes['storeOnServer'] == '1'
488
+ xml.elements.each('//location') do |loc|
489
+ location = loc.text
490
+ end
491
+ end
492
+
493
+ email = Email.parse(xml)
494
+
495
+ return Delivery.new(on_server, location, email)
496
+ end
497
+ nil
498
+ end
499
+ end
500
+
501
+ # Configuration structure for database exporting of reports.
502
+ class DBExport
503
+ # The DB type to export to.
504
+ attr_accessor :type
505
+ # Credentials needed to export to the specified database.
506
+ attr_accessor :credentials
507
+ # Map of parameters for this DB export configuration.
508
+ attr_accessor :parameters
509
+
510
+ def initialize(type)
511
+ @type = type
512
+ @parameters = {}
513
+ end
514
+
515
+ def to_xml
516
+ xml = %Q{<DBExport type='#{@type}'>}
517
+ xml << @credentials.to_xml if @credentials
518
+ @parameters.each_pair do |name, value|
519
+ xml << %Q{<param name='#{name}'>#{value}</param>}
520
+ end
521
+ xml << '</DBExport>'
522
+ end
523
+
524
+ def self.parse(xml)
525
+ xml.elements.each('//DBExport') do |dbexport|
526
+ config = DBExport.new(dbexport.attributes['type'])
527
+ config.credentials = ExportCredential.parse(xml)
528
+ xml.elements.each('//param') do |param|
529
+ config.parameters[param.attributes['name']] = param.text
530
+ end
531
+ return config
532
+ end
533
+ nil
534
+ end
535
+ end
536
+
537
+ # DBExport credentials configuration object.
538
+ #
539
+ # The user_id, password and realm attributes should ONLY be used
540
+ # if a security blob cannot be generated and the data is being
541
+ # transmitted/stored using external encryption (e.g., HTTPS).
542
+ class ExportCredential
543
+ # Security blob for exporting to a database.
544
+ attr_accessor :credential
545
+ attr_accessor :user_id
546
+ attr_accessor :password
547
+ # DB specific, usually the database name.
548
+ attr_accessor :realm
549
+
550
+ def initialize(credential)
551
+ @credential = credential
552
+ end
553
+
554
+ def to_xml
555
+ xml = '<credentials'
556
+ xml << %Q{ userid='#{@user_id}'} if @user_id
557
+ xml << %Q{ password='#{@password}'} if @password
558
+ xml << %Q{ realm='#{@realm}'} if @realm
559
+ xml << '>'
560
+ xml << @credential if @credential
561
+ xml << '</credentials>'
562
+ end
563
+
564
+ def self.parse(xml)
565
+ xml.elements.each('//credentials') do |creds|
566
+ credential = ExportCredential.new(creds.text)
567
+ # The following attributes may not exist.
568
+ credential.user_id = creds.attributes['userid']
569
+ credential.password = creds.attributes['password']
570
+ credential.realm = creds.attributes['realm']
571
+ return credential
572
+ end
573
+ nil
574
+ end
575
+ end
576
+
577
+ # Data object for report template summary information.
578
+ # Not meant for use in creating new templates.
579
+ class ReportTemplateSummary
580
+ # The ID of the report template.
581
+ attr_reader :id
582
+ # The name of the report template.
583
+ attr_reader :name
584
+ # One of: data|document. With a data template, you can export
585
+ # comma-separated value (CSV) files with vulnerability-based data.
586
+ # With a document template, you can create PDF, RTF, HTML, or XML reports
587
+ # with asset-based information.
588
+ attr_reader :type
589
+ # The visibility (scope) of the report template. One of: global|silo
590
+ attr_reader :scope
591
+ # Whether the report template is built-in, and therefore cannot be modified.
592
+ attr_reader :built_in
593
+ # Description of the report template.
594
+ attr_reader :description
595
+
596
+ def initialize(id, name, type, scope, built_in, description)
597
+ @id = id
598
+ @name = name
599
+ @type = type
600
+ @scope = scope
601
+ @built_in = built_in
602
+ @description = description
603
+ end
604
+
605
+ def self.parse(xml)
606
+ description = nil
607
+ xml.elements.each('description') { |desc| description = desc.text }
608
+ ReportTemplateSummary.new(xml.attributes['id'],
609
+ xml.attributes['name'],
610
+ xml.attributes['type'],
611
+ xml.attributes['scope'],
612
+ xml.attributes['builtin'] == '1',
613
+ description)
614
+ end
615
+ end
616
+
617
+ # Definition object for a report template.
618
+ class ReportTemplate
619
+ # The ID of the report template.
620
+ attr_accessor :id
621
+ # The name of the report template.
622
+ attr_accessor :name
623
+ # With a data template, you can export comma-separated value (CSV) files
624
+ # with vulnerability-based data. With a document template, you can create
625
+ # PDF, RTF, HTML, or XML reports with asset-based information. When you
626
+ # retrieve a report template, the type will always be visible even though
627
+ # type is implied. When ReportTemplate is sent as a request, and the type
628
+ # attribute is not provided, the type attribute defaults to document,
629
+ # allowing for backward compatibility with existing API clients.
630
+ attr_accessor :type
631
+ # The visibility (scope) of the report template.
632
+ # One of: global|silo
633
+ attr_accessor :scope
634
+ # The report template is built-in, and cannot be modified.
635
+ attr_accessor :built_in
636
+ # Description of this report template.
637
+ attr_accessor :description
638
+
639
+ # Array of report sections.
640
+ attr_accessor :sections
641
+ # Map of report properties.
642
+ attr_accessor :properties
643
+ # Array of report attributes, in the order they will be present in a report.
644
+ attr_accessor :attributes
645
+ # Display asset names with IPs.
646
+ attr_accessor :show_device_names
647
+
648
+ def initialize(name, type = 'document', id = -1, scope = 'silo', built_in = false)
649
+ @name = name
650
+ @type = type
651
+ @id = id
652
+ @scope = scope
653
+ @built_in = built_in
654
+
655
+ @sections = []
656
+ @properties = {}
657
+ @attributes = []
658
+ @show_device_names = false
659
+ end
660
+
661
+ # Save the configuration for a report template.
662
+ def save(connection)
663
+ xml = %Q{<ReportTemplateSaveRequest session-id='#{connection.session_id}' scope='#{@scope}'>}
664
+ xml << to_xml
665
+ xml << '</ReportTemplateSaveRequest>'
666
+ response = connection.execute(xml)
667
+ if response.success
668
+ @id = response.attributes['template-id']
669
+ end
670
+ end
671
+
672
+ def delete(connection)
673
+ xml = %Q{<ReportTemplateDeleteRequest session-id='#{connection.session_id}' template-id='#{@id}'>}
674
+ xml << '</ReportTemplateDeleteRequest>'
675
+ response = connection.execute(xml)
676
+ if response.success
677
+ @id = response.attributes['template-id']
678
+ end
679
+ end
680
+
681
+ # Retrieve the configuration for a report template.
682
+ def self.get(connection, template_id)
683
+ connection.get_report_template(template_id)
684
+ end
685
+
686
+ include Sanitize
687
+
688
+ def to_xml
689
+ xml = %Q{<ReportTemplate id='#{@id}' name='#{@name}' type='#{@type}'}
690
+ xml << %Q{ scope='#{@scope}'} if @scope
691
+ xml << %Q{ builtin='#{@built_in}'} if @built_in
692
+ xml << '>'
693
+ xml << %Q{<description>#{@description}</description>} if @description
694
+
695
+ unless @attributes.empty?
696
+ xml << '<ReportAttributes>'
697
+ @attributes.each do |attr|
698
+ xml << %Q(<ReportAttribute name='#{attr}'/>)
699
+ end
700
+ xml << '</ReportAttributes>'
701
+ end
702
+
703
+ unless @sections.empty?
704
+ xml << '<ReportSections>'
705
+ properties.each_pair do |name, value|
706
+ xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
707
+ end
708
+ @sections.each { |section| xml << section.to_xml }
709
+ xml << '</ReportSections>'
710
+ end
711
+
712
+ xml << %Q{<Settings><showDeviceNames enabled='#{@show_device_names ? 1 : 0}' /></Settings>}
713
+ xml << '</ReportTemplate>'
714
+ end
715
+
716
+ def self.parse(xml)
717
+ xml.res.elements.each('//ReportTemplate') do |tmp|
718
+ template = ReportTemplate.new(tmp.attributes['name'],
719
+ tmp.attributes['type'],
720
+ tmp.attributes['id'],
721
+ tmp.attributes['scope'] || 'silo',
722
+ tmp.attributes['builtin'])
723
+ tmp.elements.each('//description') do |desc|
724
+ template.description = desc.text
725
+ end
726
+
727
+ tmp.elements.each('//ReportAttributes/ReportAttribute') do |attr|
728
+ template.attributes << attr.attributes['name']
729
+ end
730
+
731
+ tmp.elements.each('//ReportSections/property') do |property|
732
+ template.properties[property.attributes['name']] = property.text
733
+ end
734
+
735
+ tmp.elements.each('//ReportSection') do |section|
736
+ template.sections << Section.parse(section)
737
+ end
738
+
739
+ tmp.elements.each('//showDeviceNames') do |show|
740
+ template.show_device_names = show.attributes['enabled'] == '1'
741
+ end
742
+
743
+ return template
744
+ end
745
+ nil
746
+ end
747
+ end
748
+
749
+ # Section specific content to include in a report template.
750
+ class Section
751
+ # Name of the report section.
752
+ attr_accessor :name
753
+ # Map of properties specific to the report section.
754
+ attr_accessor :properties
755
+
756
+ def initialize(name)
757
+ @name = name
758
+ @properties = {}
759
+ end
760
+
761
+ include Sanitize
762
+
763
+ def to_xml
764
+ xml = %Q{<ReportSection name='#{@name}'>}
765
+ properties.each_pair do |name, value|
766
+ xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
767
+ end
768
+ xml << '</ReportSection>'
769
+ end
770
+
771
+ def self.parse(xml)
772
+ name = xml.attributes['name']
773
+ xml.elements.each("//ReportSection[@name='#{name}']") do |elem|
774
+ section = Section.new(name)
775
+ elem.elements.each("//ReportSection[@name='#{name}']/property") do |property|
776
+ section.properties[property.attributes['name']] = property.text
777
+ end
778
+ return section
779
+ end
780
+ nil
781
+ end
782
+ end
783
+ end