nexpose 0.9.8 → 1.0.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.
@@ -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.