nexpose 0.0.98 → 0.1.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.
@@ -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