nexpose 0.0.9 → 0.0.91

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.
@@ -0,0 +1,108 @@
1
+ module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ #
6
+ # Create a NeXpose ticket
7
+ #
8
+ # ticket_info: A hash of the data to be used to create a ticket in NeXpose:
9
+ # :name => The name of the ticket (Required)
10
+ # :device_id => The NeXpose device ID for the device being ticketed (Required)
11
+ # :assigned_to => The NeXpose user to whom this ticket is assigned (Required)
12
+ # :priority => "low,moderate,normal,high,critical" (Required)
13
+ #
14
+ # :vulnerabilities => An array of NeXpose vuln IDs. This is NOT the same as vuln ID. (Required)
15
+ # :comments => An array of comments to accompany this ticket
16
+ #
17
+ # @return The ticket ID if the ticket creation was successful, {@code false} otherwise
18
+ #
19
+ def create_ticket ticket_info
20
+ ticket_name = ticket_info[:name]
21
+ unless ticket_name
22
+ raise ArgumentError.new 'Ticket name is required'
23
+ end
24
+
25
+ device_id = ticket_info[:device_id]
26
+ unless device_id
27
+ raise ArgumentError.new 'Device ID is required'
28
+ end
29
+
30
+ assigned_to = ticket_info[:assigned_to]
31
+ unless assigned_to
32
+ raise ArgumentError.new 'Assignee name is required'
33
+ end
34
+
35
+ priority = ticket_info[:priority]
36
+ unless priority
37
+ raise ArgumentError.new 'Ticket priority is required'
38
+ end
39
+
40
+ vulnerabilities = ticket_info[:vulnerabilities]
41
+ if not vulnerabilities or vulnerabilities.count < 1
42
+ raise ArgumentError.new 'Vulnerabilities are required'
43
+ end
44
+
45
+ comments = ticket_info[:comments]
46
+ base_xml = make_xml 'TicketCreateRequest'
47
+
48
+ required_attributes = {
49
+ 'name' => ticket_name,
50
+ 'priority' => priority,
51
+ 'device-id' => device_id,
52
+ 'assigned-to' => assigned_to
53
+ }
54
+
55
+ create_request_xml = REXML::Element.new 'TicketCreate'
56
+ create_request_xml.add_attributes required_attributes
57
+
58
+ # Add vulnerabilities
59
+ vulnerabilities_xml = REXML::Element.new 'Vulnerabilities'
60
+ vulnerabilities.each do |vuln_id|
61
+ vulnerabilities_xml.add_element 'Vulnerability', {'id' => vuln_id}
62
+ end
63
+ create_request_xml.add_element vulnerabilities_xml
64
+
65
+ # Add comments
66
+ if comments and comments.count > 0
67
+ comments_xml = REXML::Element.new 'Comments'
68
+ comments.each do |comment|
69
+ comment_xml = REXML::Element.new 'Comment'
70
+ comment_xml.add_text comment
71
+ comments_xml.add_element comment_xml
72
+ end
73
+
74
+ create_request_xml.add_element comments_xml
75
+ end
76
+
77
+ base_xml.add_element create_request_xml
78
+ r = execute base_xml, '1.2'
79
+ if r.success
80
+ r.res.elements.each('TicketCreateResponse') do |group|
81
+ return group.attributes['id'].to_i
82
+ end
83
+ else
84
+ false
85
+ end
86
+ end
87
+
88
+ #
89
+ # Deletes a NeXpose ticket.
90
+ #
91
+ # ticket_ids: An array of ticket IDs to be deleted.
92
+ #
93
+ # @returns {@code true} iff the call was successfull. {@code false} otherwise.
94
+ #
95
+ def delete_ticket ticket_ids
96
+ if not ticket_ids or ticket_ids.count < 1
97
+ raise ArgumentError.new 'The tickets to delete should not be null or empty'
98
+ end
99
+
100
+ base_xml = make_xml 'TicketDeleteRequest'
101
+ ticket_ids.each do |id|
102
+ base_xml.add_element 'Ticket', {'id' => id}
103
+ end
104
+
105
+ (execute base_xml, '1.2').success
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,35 @@
1
+ module Nexpose
2
+
3
+ module Sanitize
4
+ def replace_entities(str)
5
+ ret = str.dup
6
+ ret.gsub!(/&/, "&amp;")
7
+ ret.gsub!(/'/, "&apos;")
8
+ ret.gsub!(/"/, "&quot;")
9
+ ret.gsub!(/</, "&lt;")
10
+ ret.gsub!(/>/, "&gt;")
11
+ ret
12
+ end
13
+ end
14
+
15
+ module XMLUtils
16
+ def parse_xml(xml)
17
+ ::REXML::Document.new(xml.to_s)
18
+ end
19
+
20
+ def make_xml(name, opts={}, data='', append_session_id=true)
21
+ xml = REXML::Element.new(name)
22
+ if (@session_id and append_session_id)
23
+ xml.attributes['session-id'] = @session_id
24
+ end
25
+
26
+ opts.keys.each do |k|
27
+ xml.attributes[k] = "#{opts[k]}"
28
+ end
29
+
30
+ xml.text = data
31
+
32
+ xml
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,520 @@
1
+ module Nexpose
2
+ module NexposeAPI
3
+ include XMLUtils
4
+
5
+ ###################
6
+ # VULN EXCEPTIONS #
7
+ ###################
8
+
9
+ #-----------------------------------------------------------------------
10
+ # Returns an array of vulnerability exceptions and their associated
11
+ # attributes.
12
+ #
13
+ # @param status - (optional) The status of the vulnerability exception:
14
+ # "Under Review", "Approved", "Rejected"
15
+ #-----------------------------------------------------------------------
16
+ def vuln_listing status=nil
17
+ option = {}
18
+
19
+ if status && !status.empty?
20
+ if status =~ /Under Review|Approved|Rejected/
21
+ option['status'] = status
22
+ else
23
+ raise ArgumentError.new 'The vulnerability status passed in is invalid!'
24
+ end
25
+ end
26
+
27
+ xml = make_xml('VulnerabilityExceptionListingRequest', option)
28
+ r = execute xml, '1.2'
29
+
30
+ if r.success
31
+ res = []
32
+ r.res.elements.each("//VulnerabilityException") do |ve|
33
+ submitter_comment = ve.elements['submitter-comment']
34
+ reviewer_comment = ve.elements['reviewer-comment']
35
+ res << {
36
+ :vuln_id => ve.attributes['vuln-id'],
37
+ :exception_id => ve.attributes['exception-id'],
38
+ :submitter => ve.attributes['submitter'],
39
+ :reviewer => ve.attributes['reviewer'],
40
+ :status => ve.attributes['status'],
41
+ :reason => ve.attributes['reason'],
42
+ :scope => ve.attributes['scope'],
43
+ :device_id => ve.attributes['device-id'],
44
+ :port_no => ve.attributes['port-no'],
45
+ :expiration_date => ve.attributes['expiration-date'],
46
+ :vuln_key => ve.attributes['vuln-key'],
47
+ :submitter_comment => submitter_comment.nil? ? '' : submitter_comment.text,
48
+ :reviewer_comment => reviewer_comment.nil? ? '' : reviewer_comment.text
49
+ }
50
+ end
51
+ res
52
+ else
53
+ false
54
+ end
55
+ end
56
+
57
+ #-------------------------------------------------------------------------------------------------------------------
58
+ # Creates a vulnerability exception.
59
+ #
60
+ # @param input - data used to create the vulnerability exception:
61
+ # :vuln_id - The Nexpose vulnerability ID.
62
+ # :reason - The reason for the exception
63
+ # values - "False Positive", "Compensating Control", "Acceptable Use", "Acceptable Risk", "Other"
64
+ # :scope - The scope type (NOTE: The case is important)
65
+ # values - "All Instances", "All Instances on a Specific Asset", "Specific Instance of a specific Asset"
66
+ # :comment - A user comment
67
+ # :device-id - Used for specific instances related to "All Instances on a Specific Asset" AND "Specific Instance of Specific Asset"
68
+ # :port - All assets on this port related to "Specific Instance of a specific Asset"
69
+ # :vuln-key - The vulnerability key related to the "Specific Instance of a specific Asset"
70
+ #
71
+ # @returns exception-id - The Id associated with this create request
72
+ #-------------------------------------------------------------------------------------------------------------------
73
+ def vuln_exception_create input
74
+ options = {}
75
+
76
+ if input.nil?
77
+ raise ArgumentError.new 'The input element cannot be null'
78
+ end
79
+
80
+ vuln_id = input[:vuln_id]
81
+ if !vuln_id
82
+ raise ArgumentError.new 'The vulnerability ID is required'
83
+ end
84
+ options['vuln-id'] = vuln_id
85
+
86
+ reason = input[:reason]
87
+ if reason.nil? || reason.empty?
88
+ raise ArgumentError.new 'The reason is required'
89
+ end
90
+
91
+ unless reason =~ /False Positive|Compensating Control|Acceptable Use|Acceptable Risk|Other/
92
+ raise ArgumentError.new 'The reason type is invalid'
93
+ end
94
+ options['reason'] = reason
95
+
96
+ scope = input[:scope]
97
+ if scope.nil? || scope.empty?
98
+ raise ArgumentError.new 'The scope is required'
99
+ end
100
+
101
+ # For scope case matters.
102
+ unless scope =~ /All Instances|All Instances on a Specific Asset|Specific Instance of Specific Asset/
103
+ raise ArgumentError.new 'The scope type is invalid'
104
+ end
105
+
106
+ if scope =~ /All Instances on a Specific Asset|Specific Instance of Specific Asset/
107
+ device_id = input[:device_id]
108
+ vuln_key = input[:vuln_key]
109
+ port = input[:port]
110
+ if device_id
111
+ options['device-id'] = device_id
112
+ end
113
+
114
+ if (scope =~ /All Instances on a Specific Asset/ && (vuln_key || port))
115
+ raise ArgumentError.new "Vulnerability key or port cannot be used with the scope specified"
116
+ end
117
+
118
+ if vuln_key
119
+ options['vuln-key'] = vuln_key
120
+ end
121
+
122
+ if port
123
+ options['port-no'] = port
124
+ end
125
+ end
126
+ options['scope'] = scope
127
+
128
+ xml = make_xml('VulnerabilityExceptionCreateRequest', options)
129
+
130
+ comment = input[:comment]
131
+ if comment && !comment.empty?
132
+ comment_xml = make_xml('comment', {}, comment, false)
133
+ xml.add_element comment_xml
134
+ else
135
+ raise ArgumentError.new 'The comment cannot be empty'
136
+ end
137
+
138
+ r = execute xml, '1.2'
139
+ if r.success
140
+ r.res.elements.each("//VulnerabilityExceptionCreateResponse") do |vecr|
141
+ return vecr.attributes['exception-id']
142
+ end
143
+ else
144
+ false
145
+ end
146
+ end
147
+
148
+ #-------------------------------------------------------------------------------------------------------------------
149
+ # Resubmit a vulnerability exception.
150
+ #
151
+ # @param input - data used to create the vulnerability exception:
152
+ # :vuln_id - The Nexpose vulnerability ID. (required)
153
+ # :reason - The reason for the exception (optional)
154
+ # values - "False Positive", "Compensating Control", "Acceptable Use", "Acceptable Risk", "Other"
155
+ # :comment - A user comment (required)
156
+ #-------------------------------------------------------------------------------------------------------------------
157
+ def vuln_exception_resubmit input
158
+ options = {}
159
+
160
+ if input.nil?
161
+ raise ArgumentError.new 'The input element cannot be null'
162
+ end
163
+
164
+ exception_id = input[:exception_id]
165
+ if !exception_id
166
+ raise ArgumentError.new 'The exception ID is required'
167
+ end
168
+ options['exception-id'] = exception_id
169
+
170
+ reason = input[:reason]
171
+ if !reason.nil? && !reason.empty?
172
+ unless reason =~ /False Positive|Compensating Control|Acceptable Use|Acceptable Risk|Other/
173
+ raise ArgumentError.new 'The reason type is invalid'
174
+ end
175
+ options['reason'] = reason
176
+
177
+ end
178
+
179
+ xml = make_xml('VulnerabilityExceptionResubmitRequest', options)
180
+
181
+ comment = input[:comment]
182
+ if comment && !comment.empty?
183
+ comment_xml = make_xml('comment', {}, comment, false)
184
+ xml.add_element comment_xml
185
+ end
186
+
187
+ r = execute xml, '1.2'
188
+ r.success
189
+ end
190
+
191
+ #-------------------------------------------------------------------------------------------------------------------
192
+ # Allows a previously submitted exception that has not been approved to be withdrawn.
193
+ #
194
+ # @param exception_id - The exception id returned after the vuln exception was submitted for creation.
195
+ #-------------------------------------------------------------------------------------------------------------------
196
+ def vuln_exception_recall exception_id
197
+ xml = make_xml('VulnerabilityExceptionRecallRequest', {'exception-id' => exception_id})
198
+ r = execute xml, '1.2'
199
+ r.success
200
+ end
201
+
202
+
203
+ #-------------------------------------------------------------------------------------------------------------------
204
+ # Allows a submitted vulnerability exception to be approved.
205
+ #
206
+ # @param input:
207
+ # :exception_id - The exception id returned after the vuln exception was submitted for creation.
208
+ # :comment - An optional comment
209
+ #-------------------------------------------------------------------------------------------------------------------
210
+ def vuln_exception_approve input
211
+ exception_id = input[:exception_id]
212
+ if !exception_id
213
+ raise ArgumentError.new 'Exception Id is required'
214
+ end
215
+
216
+ xml = make_xml('VulnerabilityExceptionApproveRequest', {'exception-id' => exception_id})
217
+ comment = input[:comment]
218
+ if comment && !comment.empty?
219
+ comment_xml = make_xml('comment', {}, comment, false)
220
+ xml.add_element comment_xml
221
+ end
222
+
223
+ r = execute xml, '1.2'
224
+ r.success
225
+ end
226
+
227
+ #-------------------------------------------------------------------------------------------------------------------
228
+ # Rejects a submitted vulnerability exception to be approved.
229
+ #
230
+ # @param input:
231
+ # :exception_id - The exception id returned after the vuln exception was submitted for creation.
232
+ # :comment - An optional comment
233
+ #-------------------------------------------------------------------------------------------------------------------
234
+ def vuln_exception_reject input
235
+ exception_id = input[:exception_id]
236
+ if !exception_id
237
+ raise ArgumentError.new 'Exception Id is required'
238
+ end
239
+
240
+ xml = make_xml('VulnerabilityExceptionRejectRequest', {'exception-id' => exception_id})
241
+ comment = input[:comment]
242
+ if comment && !comment.empty?
243
+ comment_xml = make_xml('comment', {}, comment, false)
244
+ xml.add_element comment_xml
245
+ end
246
+
247
+ r = execute xml, '1.2'
248
+ r.success
249
+ end
250
+
251
+ #-------------------------------------------------------------------------------------------------------------------
252
+ # Updates a vulnerability exception comment.
253
+ #
254
+ # @param input:
255
+ # :exception_id - The exception id returned after the vuln exception was submitted for creation.
256
+ # :submitter_comment - The submitter comment
257
+ # :reviewer_comment - The reviewer comment
258
+ #-------------------------------------------------------------------------------------------------------------------
259
+ def vuln_exception_update_comment input
260
+ exception_id = input[:exception_id]
261
+ if !exception_id
262
+ raise ArgumentError.new 'Exception Id is required'
263
+ end
264
+
265
+ xml = make_xml('VulnerabilityExceptionUpdateCommentRequest', {'exception-id' => exception_id})
266
+ submitter_comment = input[:submitter_comment]
267
+ if submitter_comment && !submitter_comment.empty?
268
+ comment_xml = make_xml('submitter-comment', {}, submitter_comment, false)
269
+ xml.add_element comment_xml
270
+ end
271
+
272
+ reviewer_comment = input[:reviewer_comment]
273
+ if reviewer_comment && !reviewer_comment.empty?
274
+ comment_xml = make_xml('reviewer-comment', {}, reviewer_comment, false)
275
+ xml.add_element comment_xml
276
+ end
277
+
278
+ r = execute xml, '1.2'
279
+ r.success
280
+ end
281
+
282
+ #-------------------------------------------------------------------------------------------------------------------
283
+ # Update the expiration date for a vulnerability exception.
284
+ #
285
+ # @param input
286
+ # :exception_id - The exception id returned after the vulnerability exception was submitted for creation.
287
+ # :expiration_date - The new expiration date format: YYYY-MM-DD
288
+ #-------------------------------------------------------------------------------------------------------------------
289
+ def vuln_exception_update_expiration_date input
290
+ exception_id = input[:exception_id]
291
+ if !exception_id
292
+ raise ArgumentError.new 'Exception Id is required'
293
+ end
294
+
295
+ expiration_date = input[:expiration_date]
296
+ if expiration_date && !expiration_date.empty? && expiration_date =~ /\A\d{4}-(\d{2})-(\d{2})\z/
297
+ if $1.to_i > 12
298
+ raise ArgumentError.new 'The expiration date month value is invalid'
299
+ end
300
+
301
+ if $2.to_i > 31
302
+ raise ArgumentError.new 'The expiration date day value is invalid'
303
+ end
304
+ else
305
+ raise ArgumentError.new 'Expiration date is invalid'
306
+ end
307
+
308
+ options = {}
309
+ options['exception-id'] = exception_id
310
+ options['expiration-date'] = expiration_date
311
+ xml = make_xml('VulnerabilityExceptionUpdateExpirationDateRequest', options)
312
+ r = execute xml, '1.2'
313
+ r.success
314
+ end
315
+
316
+ #-------------------------------------------------------------------------------------------------------------------
317
+ # Deletes a submitted vulnerability exception to be approved.
318
+ #
319
+ # @param exception_id - The exception id returned after the vuln exception was submitted for creation.
320
+ #-------------------------------------------------------------------------------------------------------------------
321
+ def vuln_exception_delete exception_id
322
+ if !exception_id
323
+ raise ArgumentError.new 'Exception Id is required'
324
+ end
325
+
326
+ xml = make_xml('VulnerabilityExceptionDeleteRequest', {'exception-id' => exception_id})
327
+ r = execute xml, '1.2'
328
+ r.success
329
+ end
330
+ end
331
+
332
+ # === Description
333
+ # Object that represents a listing of all of the vulnerabilities in the vulnerability database
334
+ #
335
+ class VulnerabilityListing
336
+
337
+ # true if an error condition exists; false otherwise
338
+ attr_reader :error
339
+ # Error message string
340
+ attr_reader :error_msg
341
+ # The last XML request sent by this object
342
+ attr_reader :request_xml
343
+ # The last XML response received by this object
344
+ attr_reader :response_xml
345
+ # The NSC Connection associated with this object
346
+ attr_reader :connection
347
+ # Array containing (VulnerabilitySummary*)
348
+ attr_reader :vulnerability_summaries
349
+ # The number of vulnerability definitions
350
+ attr_reader :vulnerability_count
351
+
352
+ # Constructor
353
+ # VulnerabilityListing(connection)
354
+ def initialize(connection)
355
+ @error = false
356
+ @vulnerability_summaries = []
357
+ @connection = connection
358
+
359
+ r = @connection.execute('<VulnerabilityListingRequest session-id="' + @connection.session_id + '"/>')
360
+
361
+ if (r.success)
362
+ r.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |v|
363
+ @vulnerability_summaries.push(VulnerabilitySummary.new(v.attributes['id'], v.attributes["title"], v.attributes["severity"]))
364
+ end
365
+ else
366
+ @error = true
367
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
368
+ end
369
+ @vulnerability_count = @vulnerability_summaries.length
370
+ end
371
+ end
372
+
373
+ # === Description
374
+ # Object that represents the summary of an entry in the vulnerability database
375
+ #
376
+ class VulnerabilitySummary
377
+
378
+ # The unique ID string for this vulnerability
379
+ attr_reader :id
380
+ # The title of this vulnerability
381
+ attr_reader :title
382
+ # The severity of this vulnerability (1 – 10)
383
+ attr_reader :severity
384
+
385
+ # Constructor
386
+ # VulnerabilitySummary(id, title, severity)
387
+ def initialize(id, title, severity)
388
+ @id = id
389
+ @title = title
390
+ @severity = severity
391
+
392
+ end
393
+
394
+ end
395
+
396
+ # === Description
397
+ # Object that represents the details for an entry in the vulnerability database
398
+ #
399
+ class VulnerabilityDetail
400
+ # true if an error condition exists; false otherwise
401
+ attr_reader :error
402
+ # Error message string
403
+ attr_reader :error_msg
404
+ # The last XML request sent by this object
405
+ attr_reader :request_xml
406
+ # The last XML response received by this object
407
+ attr_reader :response_xml
408
+ # The NSC Connection associated with this object
409
+ attr_reader :connection
410
+ # The unique ID string for this vulnerability
411
+ attr_reader :id
412
+ # The title of this vulnerability
413
+ attr_reader :title
414
+ # The severity of this vulnerability (1 – 10)
415
+ attr_reader :severity
416
+ # The pciSeverity of this vulnerability
417
+ attr_reader :pciSeverity
418
+ # The CVSS score of this vulnerability
419
+ attr_reader :cvssScore
420
+ # The CVSS vector of this vulnerability
421
+ attr_reader :cvssVector
422
+ # The date this vulnerability was published
423
+ attr_reader :published
424
+ # The date this vulnerability was added to NeXpose
425
+ attr_reader :added
426
+ # The last date this vulnerability was modified
427
+ attr_reader :modified
428
+ # The HTML Description of this vulnerability
429
+ attr_reader :description
430
+ # External References for this vulnerability
431
+ # Array containing (Reference)
432
+ attr_reader :references
433
+ # The HTML Solution for this vulnerability
434
+ attr_reader :solution
435
+
436
+ # Constructor
437
+ # VulnerabilityListing(connection,id)
438
+ def initialize(connection, id)
439
+
440
+ @error = false
441
+ @connection = connection
442
+ @id = id
443
+ @references = []
444
+
445
+ r = @connection.execute('<VulnerabilityDetailsRequest session-id="' + @connection.session_id + '" vuln-id="' + @id + '"/>')
446
+
447
+ if (r.success)
448
+ r.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |v|
449
+ @id = v.attributes['id']
450
+ @title = v.attributes["title"]
451
+ @severity = v.attributes["severity"]
452
+ @pciSeverity = v.attributes['pciSeverity']
453
+ @cvssScore = v.attributes['cvssScore']
454
+ @cvssVector = v.attributes['cvssVector']
455
+ @published = v.attributes['published']
456
+ @added = v.attributes['added']
457
+ @modified = v.attributes['modified']
458
+
459
+ v.elements.each('description') do |d|
460
+ @description = d.to_s.gsub(/\<\/?description\>/i, '')
461
+ end
462
+
463
+ v.elements.each('solution') do |s|
464
+ @solution = s.to_s.gsub(/\<\/?solution\>/i, '')
465
+ end
466
+
467
+ v.elements.each('references/reference') do |r|
468
+ @references.push(Reference.new(r.attributes['source'], r.text))
469
+ end
470
+ end
471
+ else
472
+ @error = true
473
+ @error_msg = 'VulnerabilitySummaryRequest Parse Error'
474
+ end
475
+
476
+ end
477
+ end
478
+
479
+ # === Description
480
+ #
481
+ class Reference
482
+
483
+ attr_reader :source
484
+ attr_reader :reference
485
+
486
+ def initialize(source, reference)
487
+ @source = source
488
+ @reference = reference
489
+ end
490
+ end
491
+
492
+ # TODO: review
493
+ # === Description
494
+ #
495
+ class VulnFilter
496
+
497
+ attr_reader :typeMask
498
+ attr_reader :maxAlerts
499
+ attr_reader :severityThreshold
500
+
501
+ def initialize(typeMask, severityThreshold, maxAlerts = -1)
502
+ @typeMask = typeMask
503
+ @maxAlerts = maxAlerts
504
+ @severityThreshold = severityThreshold
505
+ end
506
+
507
+ include Sanitize
508
+
509
+ def to_xml
510
+ xml = "<vulnFilter "
511
+ xml << %Q{ typeMask="#{replace_entities(typeMask)}"}
512
+ xml << %Q{ maxAlerts="#{replace_entities(maxAlerts)}"}
513
+ xml << %Q{ severityThreshold="#{replace_entities(severityThreshold)}"}
514
+ xml << "/>"
515
+
516
+ xml
517
+ end
518
+
519
+ end
520
+ end