nexpose 0.9.8 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,20 +27,22 @@ module Nexpose
27
27
  end
28
28
  end
29
29
 
30
- class DiscoveryConnection
30
+ class DiscoveryConnection < APIObject
31
31
  include XMLUtils
32
32
 
33
33
  module Protocol
34
- HTTP = 'HTTP'
34
+ HTTP = 'HTTP'
35
35
  HTTPS = 'HTTPS'
36
- LDAP = 'LDAP'
36
+ LDAP = 'LDAP'
37
37
  LDAPS = 'LDAPS'
38
38
  end
39
39
 
40
40
  module Type
41
- VSPHERE = 'VSPHERE'
42
- AWS = 'AWS'
43
- ACTIVESYNC = 'ACTIVESYNC'
41
+ VSPHERE = 'VSPHERE'
42
+ AWS = 'AWS'
43
+ ACTIVESYNC = 'ACTIVESYNC'
44
+ ACTIVESYNC_POWERSHELL = 'ACTIVESYNC_POWERSHELL'
45
+ ACTIVESYNC_OFFICE365 = 'ACTIVESYNC_OFFICE365'
44
46
  end
45
47
 
46
48
  # A unique identifier for this connection.
@@ -55,18 +57,30 @@ module Nexpose
55
57
  # The IP address or fully qualified domain name of the server.
56
58
  attr_accessor :address
57
59
 
60
+ # The engine ID to use for this connection.
61
+ attr_accessor :engine_id
62
+
58
63
  # A user name that can be used to log into the server.
59
64
  attr_accessor :user
60
65
 
61
66
  # The password to use when connecting with the defined user.
62
67
  attr_accessor :password
63
68
 
64
- # The protocol used for conneting to the server. One of DiscoveryConnection::Protocol
69
+ # The protocol used for connecting to the server. One of DiscoveryConnection::Protocol
65
70
  attr_accessor :protocol
66
71
 
67
72
  # The port used for connecting to the server. A valid port from 1 to 65535.
68
73
  attr_accessor :port
69
74
 
75
+ # The hostname of the exchange server to connect for exchange powershell connections
76
+ attr_accessor :exchange_hostname
77
+
78
+ # The exchange username to connect for exchange powershell connections
79
+ attr_accessor :exchange_username
80
+
81
+ # The exchange password to connect for exchange powershell connections
82
+ attr_accessor :exchange_password
83
+
70
84
  # Whether or not the connection is active.
71
85
  # Discovery is only possible when the connection is active.
72
86
  attr_accessor :status
@@ -79,7 +93,7 @@ module Nexpose
79
93
  # @param [String] user User name for credentials on this connection.
80
94
  # @param [String] password Password for credentials on this connection.
81
95
  #
82
- def initialize(name, address, user, password = nil)
96
+ def initialize(name = nil, address = nil, user = nil, password = nil)
83
97
  @name, @address, @user, @password = name, address, user, password
84
98
  @type = nil # for backwards compatibilitly, at some point should set this to Type::VSPHERE
85
99
  @id = -1
@@ -143,13 +157,17 @@ module Nexpose
143
157
 
144
158
  def as_xml
145
159
  xml = REXML::Element.new('DiscoveryConnection')
146
- xml.attributes['name'] = @name
147
- xml.attributes['address'] = @address
148
- xml.attributes['port'] = @port
149
- xml.attributes['protocol'] = @protocol
150
- xml.attributes['user-name'] = @user
151
- xml.attributes['password'] = @password
152
- xml.attributes['type'] = @type if @type
160
+ xml.attributes['name'] = @name
161
+ xml.attributes['address'] = @address
162
+ xml.attributes['port'] = @port
163
+ xml.attributes['protocol'] = @protocol
164
+ xml.attributes['user-name'] = @user
165
+ xml.attributes['password'] = @password
166
+ xml.attributes['exchange-hostname'] = @exchange_hostname if @exchange_hostname
167
+ xml.attributes['exchange-username'] = @exchange_username if @exchange_username
168
+ xml.attributes['exchange-password'] = @exchange_password if @exchange_password
169
+ xml.attributes['type'] = @type if @type
170
+ xml.attributes['engine-id'] = @engine_id if @engine_id && @engine_id != -1
153
171
  xml
154
172
  end
155
173
 
@@ -167,6 +185,70 @@ module Nexpose
167
185
  conn.status = xml.attributes['connection-status']
168
186
  conn
169
187
  end
188
+
189
+ def to_json
190
+ JSON.generate(to_h)
191
+ end
192
+
193
+ def to_h
194
+ { id: id,
195
+ name: name,
196
+ type: type
197
+ # TODO Add remaining instance fields, once it is introduced in resource object
198
+ }
199
+ end
200
+
201
+ def ==(other)
202
+ eql?(other)
203
+ end
204
+
205
+ def eql?(other)
206
+ id.eql?(other.id) &&
207
+ name.eql?(other.name) &&
208
+ type.eql?(other.type)
209
+ # TODO Add remaining instance fields, once it is introduced in resource object
210
+ end
211
+
212
+ # Override of filter criterion to account for proper JSON naming.
213
+ #
214
+ class Criterion < Nexpose::Criterion
215
+ # Convert to Hash, which can be converted to JSON for API calls.
216
+ def to_h
217
+ { operator: operator,
218
+ values: Array(value),
219
+ field_name: field }
220
+ end
221
+
222
+ # Create a Criterion object from a JSON-derived Hash.
223
+ #
224
+ # @param [Hash] json JSON-derived Hash of a Criterion object.
225
+ # @return [Criterion] Parsed object.
226
+ #
227
+ def self.parseHash(hash)
228
+ Criterion.new(hash[:field_name],
229
+ hash[:operator],
230
+ hash[:values])
231
+ end
232
+ end
233
+
234
+ # Override of filter criteria to account for different parsing from JSON.
235
+ #
236
+ class Criteria < Nexpose::Criteria
237
+ # Create a Criteria object from a Hash.
238
+ #
239
+ # @param [Hash] Hash of a Criteria object.
240
+ # @return [Criteria] Parsed object.
241
+ #
242
+ def self.parseHash(hash)
243
+ # The call returns empty JSON, so default to 'AND' if not present.
244
+ operator = hash[:operator] || 'AND'
245
+ ret = Criteria.new([], operator)
246
+ hash[:criteria].each do |c|
247
+ ret.criteria << Criterion.parseHash(c)
248
+ end
249
+ ret
250
+ end
251
+ end
170
252
  end
171
253
 
172
254
  class DiscoveredAsset
@@ -216,8 +298,51 @@ module Nexpose
216
298
  @name, @protocol, @address, @user, @password = name, protocol, address, user, password
217
299
  @type = Type::ACTIVESYNC
218
300
  @id = -1
219
- @port = 443 #port not used for mobile connection
301
+ @port = 443 # port not used for mobile connection
302
+ end
303
+ end
304
+
305
+ class MobilePowershellDiscoveryConnection < DiscoveryConnection
306
+ # Create a new Mobile Powershell discovery connection.
307
+ #
308
+ # @param [String] name Name to assign to this connection.
309
+ # @param [String] address IP or fully qualified domain name of the
310
+ # WinRM server.
311
+ # @param [String] user WinRM User name for credentials on this connection.
312
+ # @param [String] password WinRM password for credentials on this connection.
313
+ # @param [String] exchange_hostname fully qualified domain name of the exchange server
314
+ # @param [String] exchange_username Exchange User name for exchange credentials on this connection.
315
+ # @param [String] exchange_password Exchange password for exchange credentials on this connection.
316
+ #
317
+ def initialize(name, address, user, password, exchange_hostname, exchange_username, exchange_password)
318
+ @name, @address, @user, @password = name, address, user, password
319
+ @protocol = Protocol::HTTPS
320
+ @exchange_hostname, @exchange_username, @exchange_password = exchange_hostname, exchange_username, exchange_password
321
+ @type = Type::ACTIVESYNC_POWERSHELL
322
+ @id = -1
323
+ @port = 443 # port not used for mobile connection
220
324
  end
325
+ end
221
326
 
327
+ class MobileOffice365DiscoveryConnection < DiscoveryConnection
328
+ # Create a new Mobile Office365 discovery connection.
329
+ #
330
+ # @param [String] name Name to assign to this connection.
331
+ # @param [String] address IP or fully qualified domain name of the
332
+ # WinRM server.
333
+ # @param [String] user WinRM User name for credentials on this connection.
334
+ # @param [String] password WinRM password for credentials on this connection.
335
+ # @param [String] exchange_username Exchange User name for exchange credentials on this connection.
336
+ # @param [String] exchange_password Exchange password for exchange credentials on this connection.
337
+ #
338
+ def initialize(name, address, user, password, exchange_username, exchange_password)
339
+ @name, @address, @user, @password = name, address, user, password
340
+ @protocol = Protocol::HTTPS
341
+ @exchange_hostname = '' # nexpose will set to office365 server
342
+ @exchange_username, @exchange_password = exchange_username, exchange_password
343
+ @type = Type::ACTIVESYNC_OFFICE365
344
+ @id = -1
345
+ @port = 443 # port not used for mobile connection
346
+ end
222
347
  end
223
348
  end
@@ -10,10 +10,10 @@ module Nexpose
10
10
  DATACENTER ='DATACENTER'
11
11
 
12
12
  # Valid Operators: CONTAINS, NOT_CONTAINS
13
- GUEST_OS_FAMILY = 'GUEST_OS_FAMILY'
13
+ GUEST_OS_FAMILY = 'GUEST_OS_FAMILY' #Also AWS Filter
14
14
 
15
15
  # Valid Operators: IN, NOT_IN
16
- IP_ADDRESS_RANGE = 'IP_ADDRESS'
16
+ IP_ADDRESS_RANGE = 'IP_ADDRESS' #Also AWS Filter
17
17
 
18
18
  # Valid Operators: IN, NOT_IN
19
19
  # Valid Values (See Value::PowerState): ON, OFF, SUSPENDED
@@ -25,13 +25,36 @@ module Nexpose
25
25
  # Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
26
26
  VIRTUAL_MACHINE_NAME = 'VM'
27
27
 
28
- ###### Mobile Filters ######
28
+ # valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
29
+ HOST = 'HOST_NAME'
30
+
31
+ ###### AWS Filters ######
32
+ # Valid Operators: CONTAINS, NOT_CONTAINS
33
+ AVAILABILITY_ZONE = 'AVAILABILITY_ZONE'
34
+
35
+ # Valid Operators: CONTAINS, NOT_CONTAINS
36
+ INSTANCE_ID = 'INSTANCE_ID'
37
+
38
+ # Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
39
+ INSTANCE_NAME = 'INSTANCE_NAME'
40
+
41
+ # Valid Operators: IN, NOT_IN
42
+ INSTANCE_STATE = 'INSTANCE_STATE'
43
+
44
+ # Valid Operators: IN, NOT_IN
45
+ INSTANCE_TYPE = 'INSTANCE_TYPE'
46
+
47
+ # Valid Operators: IN, NOT_IN
48
+ REGION ='REGION'
49
+
50
+ ###### Mobile or Active sync Filters ######
29
51
  # Valid Operators: CONTAINS, NOT_CONTAINS
30
52
  OPERATING_SYSTEM = 'DEVICE_OPERATING_SYSTEM'
31
53
 
32
54
  # Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
33
55
  USER = 'DEVICE_USER_DISPLAY_NAME'
34
56
 
57
+
35
58
  end
36
59
 
37
60
  module Value
@@ -16,6 +16,22 @@ module Nexpose
16
16
  response.success
17
17
  end
18
18
 
19
+ # Reverses the direction of a connection to an engine
20
+ # If the connection is currently initiated from the console this method
21
+ # will have the engine initiate the connection. If the connection is
22
+ # currently initiated by the engine this method with initiate the connection
23
+ # from the console instead. Requires a restart of the console for the
24
+ # connection to be properly established.
25
+ #
26
+ # @param [Fixnum] engine_id Unique ID of the engine.
27
+ # @return [Boolean] true if the connection is successfully reversed.
28
+ #
29
+ def reverse_engine_connection(engine_id)
30
+ uri = "/api/2.1/engine/#{engine_id}/reverseConnection"
31
+ response = AJAX.put(self, uri)
32
+ response.eql?("true")
33
+ end
34
+
19
35
  # Provide a list of current scan activities for a specific Scan Engine.
20
36
  #
21
37
  # @return [Array[ScanSummary]] Array of ScanSummary objects associated with
@@ -0,0 +1,92 @@
1
+ module Nexpose
2
+ module JsonSerializer
3
+ @@namespace = 'Nexpose'
4
+
5
+ def deserialize(data)
6
+ data.each do |key, value|
7
+ if respond_to?(key)
8
+ property = value
9
+
10
+ if value.respond_to? :each
11
+ obj = resolve_type(key)
12
+
13
+ unless obj.nil?
14
+ if value.is_a?(Array)
15
+ property = value.map { |dv| ((dv.respond_to? :each) ? create_object(obj, dv).deserialize(dv): dv) }
16
+ else
17
+ property = create_object(obj, value).deserialize(value)
18
+ end
19
+ end
20
+ elsif value.is_a?(String) && value.match(/^\d{8}T\d{6}\.\d{3}/)
21
+ property = ISO8601.to_time(value)
22
+ end
23
+
24
+ instance_variable_set("@#{key}", property)
25
+ end
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ def serialize()
32
+ hash = to_hash(Hash.new)
33
+
34
+ unless hash.nil?
35
+ JSON.generate(hash)
36
+ end
37
+ end
38
+
39
+ def to_hash(hash)
40
+ self.instance_variables.each do |m|
41
+ value = self.instance_variable_get(m)
42
+ hash[m.to_s.delete('@')] = do_hash(value)
43
+ end
44
+
45
+ hash
46
+ end
47
+
48
+ private
49
+
50
+ def do_hash(obj)
51
+ if obj.is_a?(Array)
52
+ obj = obj.map do |el|
53
+ do_hash(el)
54
+
55
+ end
56
+ elsif obj.class.included_modules.include? JsonSerializer
57
+ obj = obj.to_hash(Hash.new)
58
+ end
59
+
60
+ obj
61
+ end
62
+
63
+ def create_object(obj, data)
64
+ if obj.respond_to?(:json_initializer)
65
+ obj.method(:json_initializer).call(data)
66
+ else
67
+ obj.method(:new).call
68
+ end
69
+ end
70
+
71
+ def resolve_type(field)
72
+ class_name = normalize_field(field)
73
+ type_attribute = "#{field}_type"
74
+
75
+ if self.respond_to?(type_attribute)
76
+ clazz = self.public_send(type_attribute)
77
+ elsif Object.const_get(@@namespace).const_defined?(class_name)
78
+ resolved = Object.const_get(@@namespace).const_get(class_name)
79
+ clazz = resolved if resolved.included_modules.include? JsonSerializer
80
+ end
81
+
82
+ clazz
83
+ end
84
+
85
+ def normalize_field(field)
86
+ class_name = field.to_s.split('_').map(&:capitalize!).join
87
+ class_name = 'Vulnerability' if class_name == 'Vulnerabilities'
88
+ class_name.chop! if class_name.end_with?('s')
89
+ class_name
90
+ end
91
+ end
92
+ end
data/lib/nexpose/scan.rb CHANGED
@@ -11,6 +11,16 @@ module Nexpose
11
11
  scan_devices([device])
12
12
  end
13
13
 
14
+ # Perform an ad hoc scan of a single device at a specific time.
15
+ #
16
+ # @param [Device] device Device to scan.
17
+ # @param [Array[adhoc_schedules]] list of scheduled times at which to run
18
+ # @return [Status] whether the request was successful
19
+ #
20
+ def scan_device_with_schedule(device, schedule)
21
+ scan_devices_with_schedule([device], schedule)
22
+ end
23
+
14
24
  # Perform an ad hoc scan of a subset of devices for a site.
15
25
  # Nexpose only allows devices from a single site to be submitted per
16
26
  # request.
@@ -24,17 +34,45 @@ module Nexpose
24
34
  # @return [Scan] Scan launch information.
25
35
  #
26
36
  def scan_devices(devices)
27
- site_id = devices.map { |d| d.site_id }.uniq.first
28
- xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
37
+ site_id = devices.map(&:site_id).uniq.first
38
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
29
39
  elem = REXML::Element.new('Devices')
30
40
  devices.each do |device|
31
- elem.add_element('device', { 'id' => "#{device.id}" })
41
+ elem.add_element('device', 'id' => "#{device.id}")
32
42
  end
33
43
  xml.add_element(elem)
34
44
 
35
45
  _scan_ad_hoc(xml)
36
46
  end
37
47
 
48
+ # Perform an ad hoc scan of a subset of devices for a site.
49
+ # Nexpose only allows devices from a single site to be submitted per
50
+ # request.
51
+ # Method is designed to take objects from a Device listing.
52
+ #
53
+ # For example:
54
+ # devices = nsc.devices(5)
55
+ # nsc.scan_devices(devices.take(10))
56
+ #
57
+ # @param [Array[Device]] devices List of devices to scan.
58
+ # @param [Array[adhoc_schedules]] list of scheduled times at which to run
59
+ # @return [Status] whether the request was successful
60
+ #
61
+ def scan_devices_with_schedule(devices, schedules)
62
+ site_id = devices.map(&:site_id).uniq.first
63
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
64
+ elem = REXML::Element.new('Devices')
65
+ devices.each do |device|
66
+ elem.add_element('device', 'id' => "#{device.id}")
67
+ end
68
+ xml.add_element(elem)
69
+ scheds = REXML::Element.new('Schedules')
70
+ schedules.each { |sched| scheds.add_element(sched.as_xml) }
71
+ xml.add_element(scheds)
72
+
73
+ _scan_ad_hoc_with_schedules(xml)
74
+ end
75
+
38
76
  # Perform an ad hoc scan of a single asset of a site.
39
77
  #
40
78
  # @param [Fixnum] site_id Site ID that the assets belong to.
@@ -45,6 +83,17 @@ module Nexpose
45
83
  scan_assets(site_id, [asset])
46
84
  end
47
85
 
86
+ # Perform an ad hoc scan of a single asset of a site at a specific time
87
+ #
88
+ # @param [Fixnum] site_id Site ID that the assets belong to.
89
+ # @param [HostName|IPRange] asset Asset to scan.
90
+ # @param [Array[adhoc_schedules]] list of scheduled times at which to run
91
+ # @return [Status] whether the request was successful
92
+ #
93
+ def scan_asset_with_schedule(site_id, asset, schedule)
94
+ scan_assets_with_schedule(site_id, [asset], schedule)
95
+ end
96
+
48
97
  # Perform an ad hoc scan of a subset of assets for a site.
49
98
  # Only assets from a single site should be submitted per request.
50
99
  # Method is designed to take objects filtered from Site#assets.
@@ -58,7 +107,7 @@ module Nexpose
58
107
  # @return [Scan] Scan launch information.
59
108
  #
60
109
  def scan_assets(site_id, assets)
61
- xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
110
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
62
111
  hosts = REXML::Element.new('Hosts')
63
112
  assets.each { |asset| _append_asset!(hosts, asset) }
64
113
  xml.add_element(hosts)
@@ -66,6 +115,59 @@ module Nexpose
66
115
  _scan_ad_hoc(xml)
67
116
  end
68
117
 
118
+ # Perform an ad hoc scan of a subset of assets for a site by adding a specific runtime.
119
+ # Only assets from a single site should be submitted per request.
120
+ # Method is designed to take objects filtered from Site#assets.
121
+ #
122
+ # For example:
123
+ # site = Site.load(nsc, 5)
124
+ # nsc.scan_assets_with_schedule(5, site.assets.take(10), schedules)
125
+ #
126
+ # @param [Fixnum] site_id Site ID that the assets belong to.
127
+ # @param [Array[HostName|IPRange]] assets List of assets to scan.
128
+ # @param [Array[adhoc_schedules]] list of scheduled times at which to run
129
+ # @return [Status] whether the request was successful
130
+ #
131
+ def scan_assets_with_schedule(site_id, assets, schedules)
132
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
133
+ hosts = REXML::Element.new('Hosts')
134
+ assets.each { |asset| _append_asset!(hosts, asset) }
135
+ xml.add_element(hosts)
136
+ scheds = REXML::Element.new('Schedules')
137
+ schedules.each { |sched| scheds.add_element(sched.as_xml) }
138
+ xml.add_element(scheds)
139
+
140
+ _scan_ad_hoc_with_schedules(xml)
141
+ end
142
+
143
+ # Perform an ad hoc scan of a subset of IP addresses for a site at a specific time.
144
+ # Only IPs from a single site can be submitted per request,
145
+ # and IP addresses must already be included in the site configuration.
146
+ # Method is designed for scanning when the targets are coming from an
147
+ # external source that does not have access to internal identfiers.
148
+ #
149
+ # For example:
150
+ # to_scan = ['192.168.2.1', '192.168.2.107']
151
+ # nsc.scan_ips(5, to_scan)
152
+ #
153
+ # @param [Fixnum] site_id Site ID that the assets belong to.
154
+ # @param [Array[String]] ip_addresses Array of IP addresses to scan.
155
+ # @return [Status] whether the request was successful
156
+ #
157
+ def scan_ips_with_schedule(site_id, ip_addresses, schedules)
158
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
159
+ hosts = REXML::Element.new('Hosts')
160
+ ip_addresses.each do |ip|
161
+ xml.add_element('range', 'from' => ip)
162
+ end
163
+ xml.add_element(hosts)
164
+ scheds = REXML::Element.new('Schedules')
165
+ schedules.each { |sched| scheds.add_element(sched.as_xml) }
166
+ xml.add_element(scheds)
167
+
168
+ _scan_ad_hoc_with_schedules(xml)
169
+ end
170
+
69
171
  # Perform an ad hoc scan of a subset of IP addresses for a site.
70
172
  # Only IPs from a single site can be submitted per request,
71
173
  # and IP addresses must already be included in the site configuration.
@@ -81,10 +183,10 @@ module Nexpose
81
183
  # @return [Scan] Scan launch information.
82
184
  #
83
185
  def scan_ips(site_id, ip_addresses)
84
- xml = make_xml('SiteDevicesScanRequest', { 'site-id' => site_id })
186
+ xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
85
187
  hosts = REXML::Element.new('Hosts')
86
188
  ip_addresses.each do |ip|
87
- xml.add_element('range', { 'from' => ip })
189
+ xml.add_element('range', 'from' => ip)
88
190
  end
89
191
  xml.add_element(hosts)
90
192
 
@@ -97,7 +199,7 @@ module Nexpose
97
199
  # @return [Scan] Scan launch information.
98
200
  #
99
201
  def scan_site(site_id)
100
- xml = make_xml('SiteScanRequest', { 'site-id' => site_id })
202
+ xml = make_xml('SiteScanRequest', 'site-id' => site_id)
101
203
  response = execute(xml)
102
204
  Scan.parse(response.res) if response.success
103
205
  end
@@ -110,7 +212,7 @@ module Nexpose
110
212
  #
111
213
  def _append_asset!(xml, asset)
112
214
  if asset.is_a? Nexpose::IPRange
113
- xml.add_element('range', { 'from' => asset.from, 'to' => asset.to })
215
+ xml.add_element('range', 'from' => asset.from, 'to' => asset.to)
114
216
  else # Assume HostName
115
217
  host = REXML::Element.new('host')
116
218
  host.text = asset.host
@@ -129,6 +231,15 @@ module Nexpose
129
231
  Scan.parse(r.res)
130
232
  end
131
233
 
234
+ # Utility method for executing prepared XML for adhoc with schedules
235
+ #
236
+ # @param [REXML::Document] xml Prepared API call to execute.
237
+ #
238
+ def _scan_ad_hoc_with_schedules(xml)
239
+ r = execute(xml, '1.1', timeout: 60)
240
+ r.success
241
+ end
242
+
132
243
  # Stop a running or paused scan.
133
244
  #
134
245
  # @param [Fixnum] scan_id ID of the scan to stop.
@@ -136,7 +247,7 @@ module Nexpose
136
247
  # updated.
137
248
  #
138
249
  def stop_scan(scan_id, wait_sec = 0)
139
- r = execute(make_xml('ScanStopRequest', { 'scan-id' => scan_id }))
250
+ r = execute(make_xml('ScanStopRequest', 'scan-id' => scan_id))
140
251
  if r.success
141
252
  so_far = 0
142
253
  while so_far < wait_sec
@@ -155,7 +266,7 @@ module Nexpose
155
266
  # @return [String] Current status of the scan. See Nexpose::Scan::Status.
156
267
  #
157
268
  def scan_status(scan_id)
158
- r = execute(make_xml('ScanStatusRequest', { 'scan-id' => scan_id }))
269
+ r = execute(make_xml('ScanStatusRequest', 'scan-id' => scan_id))
159
270
  r.success ? r.attributes['status'] : nil
160
271
  end
161
272
 
@@ -164,7 +275,7 @@ module Nexpose
164
275
  # @param [Fixnum] scan_id The scan ID.
165
276
  #
166
277
  def resume_scan(scan_id)
167
- r = execute(make_xml('ScanResumeRequest', { 'scan-id' => scan_id }), '1.1', timeout: 60)
278
+ r = execute(make_xml('ScanResumeRequest', 'scan-id' => scan_id), '1.1', timeout: 60)
168
279
  r.success ? r.attributes['success'] == '1' : false
169
280
  end
170
281
 
@@ -173,7 +284,7 @@ module Nexpose
173
284
  # @param [Fixnum] scan_id The scan ID.
174
285
  #
175
286
  def pause_scan(scan_id)
176
- r = execute(make_xml('ScanPauseRequest', { 'scan-id' => scan_id }))
287
+ r = execute(make_xml('ScanPauseRequest', 'scan-id' => scan_id))
177
288
  r.success ? r.attributes['success'] == '1' : false
178
289
  end
179
290
 
@@ -218,7 +329,7 @@ module Nexpose
218
329
  # @return [ScanSummary] ScanSummary object providing statistics for the scan.
219
330
  #
220
331
  def scan_statistics(scan_id)
221
- r = execute(make_xml('ScanStatisticsRequest', { 'scan-id' => scan_id }))
332
+ r = execute(make_xml('ScanStatisticsRequest', 'scan-id' => scan_id))
222
333
  if r.success
223
334
  ScanSummary.parse(r.res.elements['//ScanSummary'])
224
335
  else
@@ -353,6 +464,7 @@ module Nexpose
353
464
  def initialize(scan_id, site_id, engine_id, status, start_time, end_time)
354
465
  @scan_id, @site_id, @engine_id, @status, @start_time, @end_time = scan_id, site_id, engine_id, status, start_time, end_time
355
466
  end
467
+
356
468
  def self.parse(xml)
357
469
  # Start time can be empty in some error conditions.
358
470
  start_time = nil
@@ -382,7 +494,6 @@ module Nexpose
382
494
  # Object that represents a summary of a scan.
383
495
  #
384
496
  class ScanSummary < ScanData
385
-
386
497
  # The reason the scan was stopped or failed, if applicable.
387
498
  attr_reader :message
388
499
 
@@ -408,7 +519,7 @@ module Nexpose
408
519
  tasks = Tasks.parse(xml.elements['tasks'])
409
520
  nodes = Nodes.parse(xml.elements['nodes'])
410
521
  vulns = Vulnerabilities.parse(xml.attributes['scan-id'], xml)
411
- msg = xml.elements['message'] ? xml.elements['message'].text : nil
522
+ msg = xml.elements['message'] ? xml.elements['message'].text : nil
412
523
 
413
524
  # Start time can be empty in some error conditions.
414
525
  start_time = nil
@@ -440,7 +551,6 @@ module Nexpose
440
551
  # Value class to tracking task counts.
441
552
  #
442
553
  class Tasks
443
-
444
554
  attr_reader :pending, :active, :completed
445
555
 
446
556
  def initialize(pending, active, completed)
@@ -463,7 +573,6 @@ module Nexpose
463
573
  # Value class for tracking node counts.
464
574
  #
465
575
  class Nodes
466
-
467
576
  attr_reader :live, :dead, :filtered, :unresolved, :other
468
577
 
469
578
  def initialize(live, dead, filtered, unresolved, other)
@@ -488,7 +597,6 @@ module Nexpose
488
597
  # Value class for tracking vulnerability counts.
489
598
  #
490
599
  class Vulnerabilities
491
-
492
600
  attr_reader :vuln_exploit, :vuln_version, :vuln_potential,
493
601
  :not_vuln_exploit, :not_vuln_version,
494
602
  :error, :disabled, :other
@@ -497,11 +605,11 @@ module Nexpose
497
605
  not_vuln_exploit, not_vuln_version,
498
606
  error, disabled, other)
499
607
  @vuln_exploit, @vuln_version, @vuln_potential,
500
- @not_vuln_exploit, @not_vuln_version,
501
- @error, @disabled, @other =
502
- vuln_exploit, vuln_version, vuln_potential,
503
- not_vuln_exploit, not_vuln_version,
504
- error, disabled, other
608
+ @not_vuln_exploit, @not_vuln_version,
609
+ @error, @disabled, @other =
610
+ vuln_exploit, vuln_version, vuln_potential,
611
+ not_vuln_exploit, not_vuln_version,
612
+ error, disabled, other
505
613
  end
506
614
 
507
615
  # Parse REXML to Vulnerabilities object.