nexpose 0.0.9 → 0.0.91

Sign up to get free protection for your applications and to get access to all the features.
@@ -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