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.
@@ -4,7 +4,7 @@ module Nexpose
4
4
 
5
5
  # List the scan templates currently configured on the console.
6
6
  #
7
- # @return [Array[String]] list of scan templates IDs.
7
+ # @return [Array[ScanTemplateSummary]] list of scan template summary objects.
8
8
  #
9
9
  def list_scan_templates
10
10
  templates = JSON.parse(AJAX.get(self, '/api/2.0/scan_templates'))
@@ -0,0 +1,31 @@
1
+ module Nexpose
2
+ # SharedSecret class for pairing engines
3
+ class SharedSecret < APIObject
4
+ attr_reader :key_string
5
+ attr_reader :ttl
6
+
7
+ def initialize(console, time_to_live)
8
+ uri = "/data/admin/global/shared-secret?time-to-live=#{time_to_live}"
9
+ json = JSON.parse(AJAX.put(console, uri))
10
+ self.from_json(json)
11
+ end
12
+
13
+ def from_json(json)
14
+ @key_string = json['keyString']
15
+ @ttl = json['timeToLiveInSeconds']
16
+ end
17
+
18
+ def delete(console)
19
+ uri = "/data/admin/global/remove-shared-secret?key-string=#{key_string}"
20
+ AJAX.delete(console, uri)
21
+ end
22
+
23
+ def ==(other)
24
+ return false unless self.class == other.class
25
+ return false unless key_string.upcase == other.key_string.upcase
26
+
27
+ true
28
+ end
29
+ alias_method :eql?, :==
30
+ end
31
+ end
data/lib/nexpose/site.rb CHANGED
@@ -77,8 +77,8 @@ module Nexpose
77
77
  # Configuration object representing a Nexpose site.
78
78
  #
79
79
  # For a basic walk-through, see {https://github.com/rapid7/nexpose-client/wiki/Using-Sites}
80
- class Site
81
-
80
+ class Site < APIObject
81
+ include JsonSerializer
82
82
  # The site ID. An ID of -1 is used to designate a site that has not been
83
83
  # saved to a Nexpose console.
84
84
  attr_accessor :id
@@ -89,33 +89,41 @@ module Nexpose
89
89
  # Description of the site.
90
90
  attr_accessor :description
91
91
 
92
- # [Array] Collection of assets. May be IPv4, IPv6, or DNS names.
93
- # @see HostName
94
- # @see IPRange
95
- attr_accessor :assets
92
+ # Included scan targets. May be IPv4, IPv6, DNS names, IPRanges or assetgroup ids.
93
+ attr_accessor :included_scan_targets
96
94
 
97
- # [Array] Collection of excluded assets. May be IPv4, IPv6, or DNS names.
98
- attr_accessor :exclude
95
+ # Excluded scan targets. May be IPv4, IPv6, DNS names, IPRanges or assetgroup ids.
96
+ attr_accessor :excluded_scan_targets
99
97
 
100
- # Scan template to use when starting a scan job. Default: full-audit
101
- attr_accessor :scan_template
98
+ # Scan template to use when starting a scan job. Default: full-audit-without-web-spider
99
+ attr_accessor :scan_template_id
102
100
 
103
101
  # Friendly name of scan template to use when starting a scan job.
104
102
  # Value is populated when a site is saved or loaded from a console.
105
103
  attr_accessor :scan_template_name
106
104
 
107
105
  # Scan Engine to use. Will use the default engine if nil or -1.
108
- attr_accessor :engine
106
+ attr_accessor :engine_id
109
107
 
110
108
  # [Array] Schedule starting dates and times for scans, and set their frequency.
111
109
  attr_accessor :schedules
112
110
 
111
+
113
112
  # The risk factor associated with this site. Default: 1.0
114
113
  attr_accessor :risk_factor
115
114
 
116
115
  # [Array] Collection of credentials associated with this site. Does not
117
116
  # include shared credentials.
118
- attr_accessor :credentials
117
+ attr_accessor :site_credentials
118
+
119
+ # [Array] Collection of shared credentials associated with this site.
120
+ attr_accessor :shared_credentials
121
+
122
+ # [Array] Collection of web credentials associated with the site.
123
+ attr_accessor :web_credentials
124
+
125
+ # Scan the assets with last scanned engine or not.
126
+ attr_accessor :auto_engine_selection_enabled
119
127
 
120
128
  # [Array] Collection of real-time alerts.
121
129
  # @see Alert
@@ -134,15 +142,11 @@ module Nexpose
134
142
  # Configuration version. Default: 3
135
143
  attr_accessor :config_version
136
144
 
137
- # Whether or not this site is dynamic.
138
- # Dynamic sites are created through Asset Discovery Connections.
139
- attr_accessor :is_dynamic
140
-
141
145
  # Asset filter criteria if this site is dynamic.
142
- attr_accessor :criteria
146
+ attr_accessor :search_criteria
143
147
 
144
- # ID of the discovery connection associated with this site if it is dynamic.
145
- attr_accessor :discovery_connection_id
148
+ # discovery config of the discovery connection associated with this site if it is dynamic.
149
+ attr_accessor :discovery_config
146
150
 
147
151
  # [Array[TagSummary]] Collection of TagSummary
148
152
  attr_accessor :tags
@@ -150,166 +154,380 @@ module Nexpose
150
154
  # Site constructor. Both arguments are optional.
151
155
  #
152
156
  # @param [String] name Unique name of the site.
153
- # @param [String] scan_template ID of the scan template to use.
154
- def initialize(name = nil, scan_template = 'full-audit-without-web-spider')
157
+ # @param [String] scan_template_id ID of the scan template to use.
158
+ def initialize(name = nil, scan_template_id = 'full-audit-without-web-spider')
155
159
  @name = name
156
- @scan_template = scan_template
157
-
160
+ @scan_template_id = scan_template_id
158
161
  @id = -1
159
162
  @risk_factor = 1.0
160
163
  @config_version = 3
161
- @is_dynamic = false
162
- @assets = []
163
164
  @schedules = []
164
- @credentials = []
165
+ @included_scan_targets = { addresses: [], asset_groups: [] }
166
+ @excluded_scan_targets = { addresses: [], asset_groups: [] }
167
+ @site_credentials = []
168
+ @shared_credentials = []
169
+ @web_credentials = []
165
170
  @alerts = []
166
- @exclude = []
167
171
  @users = []
168
172
  @tags = []
169
173
  end
170
174
 
171
- # Returns true when the site is dynamic.
172
- def dynamic?
173
- is_dynamic
175
+ # Returns the array of included scan target addresses.
176
+ # @return [Array[IPRange|HostName]] Array of included addresses.
177
+ def included_addresses
178
+ @included_scan_targets[:addresses]
174
179
  end
175
180
 
176
- def discovery_connection_id=(value)
177
- @is_dynamic = true
178
- @discovery_connection_id = value.to_i
181
+ # Sets the array of included scan target addresses.
182
+ # @param [Array[IPRange|HostName]] new_addresses The new array of scan target addresses.
183
+ # @return [Array[IPRange|HostName]] Array of updated scan target addresses.
184
+ def included_addresses=(new_addresses)
185
+ @included_scan_targets[:addresses] = new_addresses
179
186
  end
180
187
 
181
- # Adds an asset to this site by host name.
182
- #
183
- # @param [String] hostname FQDN or DNS-resolvable host name of an asset.
184
- def add_host(hostname)
185
- @assets << HostName.new(hostname)
188
+ # Returns the array of IDs for included scan target asset groups.
189
+ # @return [Array[Fixnum]] Array of included asset groups.
190
+ def included_asset_groups
191
+ @included_scan_targets[:asset_groups]
186
192
  end
187
193
 
188
- # Remove an asset to this site by host name.
189
- #
190
- # @param [String] hostname FQDN or DNS-resolvable host name of an asset.
191
- def remove_host(hostname)
192
- @assets = assets.reject { |asset| asset == HostName.new(hostname) }
194
+ # Sets the array of IDs for included scan target asset groups.
195
+ # @param [Array[Fixnum] new_asset_groups The new array of IDs for scan target asset groups.
196
+ # @return [Array[Fixnum] Array of IDs of the updated scan target asset groups.
197
+ def included_asset_groups=(new_asset_groups)
198
+ @included_scan_targets[:asset_groups] = new_asset_groups
193
199
  end
194
200
 
195
- # Adds an asset to this site by IP address.
196
- #
197
- # @param [String] ip IP address of an asset.
198
- def add_ip(ip)
199
- @assets << IPRange.new(ip)
201
+ # Returns the array of excluded scan target addresses.
202
+ # @return [Array[IPRange|HostName]] Array of excluded addresses.
203
+ def excluded_addresses
204
+ @excluded_scan_targets[:addresses]
200
205
  end
201
206
 
202
- # Remove an asset to this site by IP address.
203
- #
204
- # @param [String] ip IP address of an asset.
205
- def remove_ip(ip)
206
- @assets = assets.reject { |asset| asset == IPRange.new(ip) }
207
+ # Sets the array of excluded scan target addresses.
208
+ # @param [Array[IPRange|HostName]] new_addresses The new array of scan target addresses.
209
+ # @return [Array[IPRange|HostName]] Array of updated scan target addresses.
210
+ def excluded_addresses=(new_addresses)
211
+ @excluded_scan_targets[:addresses] = new_addresses
212
+ end
213
+
214
+ # Returns the array of IDs for excluded scan target asset groups.
215
+ # @return [Array[Fixnum]] Array of IDs for excluded asset groups.
216
+ def excluded_asset_groups
217
+ @excluded_scan_targets[:asset_groups]
218
+ end
219
+
220
+ # Sets the array IDs for excluded scan target asset groups.
221
+ # @param [Array[Fixnum]] new_asset_groups The new array of IDs for scan target asset groups.
222
+ # @return [Array[Fixnum]] Array of IDs of the updated scan target asset groups.
223
+ def excluded_asset_groups=(new_asset_groups)
224
+ @excluded_scan_targets[:asset_groups] = new_asset_groups
225
+ end
226
+
227
+ # Returns true when the site is dynamic.
228
+ def is_dynamic?
229
+ !@discovery_config.nil?
207
230
  end
208
231
 
209
232
  # Adds assets to this site by IP address range.
210
233
  #
211
234
  # @param [String] from Beginning IP address of a range.
212
235
  # @param [String] to Ending IP address of a range.
236
+ def include_ip_range(from, to)
237
+ begin
238
+ from_ip = IPAddr.new(from)
239
+ to_ip = IPAddr.new(to)
240
+ (from_ip..to_ip)
241
+ if (from_ip..to_ip).to_a.size == 0
242
+ raise 'Invalid IP range specified'
243
+ end
244
+ @included_scan_targets[:addresses] << IPRange.new(from, to)
245
+ rescue ArgumentError => e
246
+ raise "#{e.message} in given IP range"
247
+ end
248
+ end
249
+
250
+ # @deprecated Use {#include_ip_range} instead.
213
251
  def add_ip_range(from, to)
214
- @assets << IPRange.new(from, to)
252
+ warn "[DEPRECATED] Use #{self.class}#include_ip_range instead of #{self.class}#add_ip_range."
253
+ include_ip_range(from, to)
215
254
  end
216
255
 
217
256
  # Remove assets to this site by IP address range.
218
257
  #
219
258
  # @param [String] from Beginning IP address of a range.
220
259
  # @param [String] to Ending IP address of a range.
260
+ def remove_included_ip_range(from, to)
261
+ begin
262
+ from_ip = IPAddr.new(from)
263
+ to_ip = IPAddr.new(to)
264
+ (from_ip..to_ip)
265
+ if (from_ip..to_ip).to_a.size == 0
266
+ raise 'Invalid IP range specified'
267
+ end
268
+ @included_scan_targets[:addresses].reject! { |t| t.eql? IPRange.new(from, to) }
269
+ rescue ArgumentError => e
270
+ raise "#{e.message} in given IP range"
271
+ end
272
+ end
273
+
274
+ # @deprecated Use {#remove_included_ip_range} instead.
221
275
  def remove_ip_range(from, to)
222
- @assets = assets.reject { |asset| asset == IPRange.new(from, to) }
276
+ warn "[DEPRECATED] Use #{self.class}#remove_included_ip_range instead of #{self.class}#remove_ip_range."
277
+ remove_included_ip_range(from, to)
223
278
  end
224
279
 
225
- # Adds an asset to this site, resolving whether an IP or hostname is
280
+ # Adds an asset to this site included scan targets, resolving whether an IP or hostname is
226
281
  # provided.
227
282
  #
228
283
  # @param [String] asset Identifier of an asset, either IP or host name.
229
284
  #
285
+ def include_asset(asset)
286
+ @included_scan_targets[:addresses] << HostOrIP.convert(asset)
287
+ end
288
+
289
+ # @deprecated Use {#include_asset} instead.
230
290
  def add_asset(asset)
231
- obj = HostOrIP.convert(asset)
232
- @assets << obj
291
+ warn "[DEPRECATED] Use #{self.class}#include_asset instead of #{self.class}#add_asset."
292
+ include_asset(asset)
233
293
  end
234
294
 
235
- # Remove an asset to this site, resolving whether an IP or hostname is
295
+ alias_method :add_host, :add_asset
296
+ alias_method :add_ip, :add_asset
297
+
298
+ # Remove an asset to this site included scan targets, resolving whether an IP or hostname is
236
299
  # provided.
237
300
  #
238
301
  # @param [String] asset Identifier of an asset, either IP or host name.
239
302
  #
303
+ def remove_included_asset(asset)
304
+ @included_scan_targets[:addresses].reject! { |existing_asset| existing_asset == HostOrIP.convert(asset) }
305
+ end
306
+
307
+ # @deprecated Use {#remove_included_asset} instead.
240
308
  def remove_asset(asset)
309
+ warn "[DEPRECATED] Use #{self.class}#remove_included_asset instead of #{self.class}#remove_asset."
310
+ remove_included_asset(asset)
311
+ end
312
+
313
+ alias_method :remove_host, :remove_asset
314
+ alias_method :remove_ip, :remove_asset
315
+
316
+ # Adds assets to this site excluded scan targets by IP address range.
317
+ #
318
+ # @param [String] from Beginning IP address of a range.
319
+ # @param [String] to Ending IP address of a range.
320
+ def exclude_ip_range(from, to)
241
321
  begin
242
- # If the asset registers as a valid IP, remove as IP.
243
- IPAddr.new(asset)
244
- remove_ip(asset)
322
+ from_ip = IPAddr.new(from)
323
+ to_ip = IPAddr.new(to)
324
+ (from_ip..to_ip)
325
+ if (from_ip..to_ip).to_a.size == 0
326
+ raise 'Invalid IP range specified'
327
+ end
328
+ @excluded_scan_targets[:addresses] << IPRange.new(from, to)
245
329
  rescue ArgumentError => e
246
- if e.message == 'invalid address'
247
- remove_host(asset)
248
- else
249
- raise "Unable to parse asset: '#{asset}'. #{e.message}"
330
+ raise "#{e.message} in given IP range"
331
+ end
332
+ end
333
+
334
+ # Remove assets from this site excluded scan targets by IP address range.
335
+ #
336
+ # @param [String] from Beginning IP address of a range.
337
+ # @param [String] to Ending IP address of a range.
338
+ def remove_excluded_ip_range(from, to)
339
+ begin
340
+ from_ip = IPAddr.new(from)
341
+ to_ip = IPAddr.new(to)
342
+ (from_ip..to_ip)
343
+ if (from_ip..to_ip).to_a.size == 0
344
+ raise 'Invalid IP range specified'
250
345
  end
346
+ @excluded_scan_targets[:addresses].reject! { |t| t.eql? IPRange.new(from, to) }
347
+ rescue ArgumentError => e
348
+ raise "#{e.message} in given IP range"
251
349
  end
252
350
  end
253
351
 
254
- # Adds an asset to this site's exclude list, resolving whether an IP or
255
- # hostname is provided.
352
+ # Adds an asset to this site excluded scan targets, resolving whether an IP or hostname is
353
+ # provided.
256
354
  #
257
355
  # @param [String] asset Identifier of an asset, either IP or host name.
258
356
  #
259
357
  def exclude_asset(asset)
260
- @exclude << HostOrIP.convert(asset)
358
+ @excluded_scan_targets[:addresses] << HostOrIP.convert(asset)
261
359
  end
262
360
 
263
- alias_method :exclude_host, :exclude_asset
264
- alias_method :exclude_ip, :exclude_asset
265
-
266
- # Remove an asset from this site's exclude list, resolving whether an IP
267
- # or hostname is provided.
361
+ # Removes an asset to this site excluded scan targets, resolving whether an IP or hostname is
362
+ # provided.
268
363
  #
269
364
  # @param [String] asset Identifier of an asset, either IP or host name.
270
365
  #
271
366
  def remove_excluded_asset(asset)
272
- @exclude.reject! { |existing_asset| existing_asset == HostOrIP.convert(asset) }
367
+ @excluded_scan_targets[:addresses].reject! { |existing_asset| existing_asset == HostOrIP.convert(asset) }
273
368
  end
274
369
 
275
- alias_method :remove_excluded_host, :remove_excluded_asset
276
- alias_method :remove_excluded_ip, :remove_excluded_asset
370
+ # Adds an asset group ID to this site included scan targets.
371
+ #
372
+ # @param [Integer] asset_group_id Identifier of an assetGroupID.
373
+ #
374
+ def include_asset_group(asset_group_id)
375
+ validate_asset_group(asset_group_id)
376
+ @included_scan_targets[:asset_groups] << asset_group_id.to_i
377
+ end
277
378
 
278
- # Adds assets to this site's exclude list by IP address range.
379
+ # Adds an asset group ID to this site included scan targets.
279
380
  #
280
- # @param [String] from Beginning IP address of a range.
281
- # @param [String] to Ending IP address of a range.
282
- def exclude_ip_range(from, to)
283
- @exclude << IPRange.new(from, to)
381
+ # @param [Integer] asset_group_id Identifier of an assetGroupID.
382
+ #
383
+ def remove_included_asset_group(asset_group_id)
384
+ validate_asset_group(asset_group_id)
385
+ @included_scan_targets[:asset_groups].reject! { |t| t.eql? asset_group_id.to_i }
284
386
  end
285
387
 
286
- # Remove assets from this site's exclude list by IP address range.
388
+ # Adds an asset group ID to this site excluded scan targets.
287
389
  #
288
- # @param [String] from Beginning IP address of a range.
289
- # @param [String] to Ending IP address of a range.
290
- def remove_excluded_ip_range(from, to)
291
- @exclude.reject! { |asset| asset == IPRange.new(from, to) }
390
+ # @param [Integer] asset_group_id Identifier of an assetGroupID.
391
+ #
392
+ def exclude_asset_group(asset_group_id)
393
+ validate_asset_group(asset_group_id)
394
+ @excluded_scan_targets[:asset_groups] << asset_group_id.to_i
292
395
  end
293
396
 
294
- # Load an existing configuration from a Nexpose instance.
397
+ # Adds an asset group ID to this site excluded scan targets.
295
398
  #
296
- # @param [Connection] connection Connection to console where site exists.
297
- # @param [Fixnum] id Site ID of an existing site.
298
- # @return [Site] Site configuration loaded from a Nexpose console.
399
+ # @param [Integer] asset_group_id Identifier of an assetGroupID.
299
400
  #
300
- def self.load(connection, id, is_extended = false)
301
- if is_extended
302
- r = APIRequest.execute(connection.url,
303
- %(<SiteConfigRequest session-id="#{connection.session_id}" site-id="#{id}" is_extended="true"/>))
304
- else
305
- r = APIRequest.execute(connection.url,
306
- %(<SiteConfigRequest session-id="#{connection.session_id}" site-id="#{id}"/>))
401
+ def remove_excluded_asset_group(asset_group_id)
402
+ validate_asset_group(asset_group_id)
403
+ @excluded_scan_targets[:asset_groups].reject! { |t| t.eql? asset_group_id.to_i }
404
+ end
405
+
406
+ def validate_asset_group(asset_group_id)
407
+ begin
408
+ Integer(asset_group_id)
409
+ rescue ArgumentError => e
410
+ raise "Invalid asset_group id. #{e.message}"
411
+ end
412
+
413
+ raise 'Invalid asset_group id. Must be positive number.' if asset_group_id.to_i < 1
414
+ end
415
+
416
+ def add_user(user_id)
417
+ unless user_id.is_a?(Numeric) && user_id > 0
418
+ raise 'Invalid user id. A user id must be a positive number and refer to an existing system user.'
419
+ end
420
+
421
+ @users << { id: user_id}
422
+ end
423
+
424
+ def remove_user(user_id)
425
+ unless user_id.is_a?(Numeric) && user_id > 0
426
+ raise 'Invalid user id. A user id must be a positive number and refer to an existing system user.'
427
+ end
428
+
429
+ @users.delete_if { |h| h[:id] == user_id }
430
+ end
431
+
432
+ def self.from_hash(hash)
433
+ site = new(hash[:name], hash[:scan_template_id])
434
+ hash.each do |k, v|
435
+ site.instance_variable_set("@#{k}", v)
307
436
  end
308
- site = parse(r.res)
309
- site.load_dynamic_attributes(connection) if site.dynamic?
437
+
438
+ # Convert each string address to either a HostName or IPRange object
439
+ included_scan_targets = { addresses: [], asset_groups: [] }
440
+ site.included_scan_targets[:addresses].each { |asset| included_scan_targets[:addresses] << HostOrIP.convert(asset) }
441
+ included_scan_targets[:asset_groups] = site.included_scan_targets[:asset_groups]
442
+ site.included_scan_targets = included_scan_targets
443
+
444
+ excluded_scan_targets = { addresses: [], asset_groups: [] }
445
+ site.excluded_scan_targets[:addresses].each { |asset| excluded_scan_targets[:addresses] << HostOrIP.convert(asset) }
446
+ excluded_scan_targets[:asset_groups] = site.excluded_scan_targets[:asset_groups]
447
+ site.excluded_scan_targets = excluded_scan_targets
448
+
310
449
  site
311
450
  end
312
451
 
452
+ def to_json
453
+ JSON.generate(to_h)
454
+ end
455
+
456
+ def to_h
457
+ included_scan_targets = {
458
+ addresses: @included_scan_targets[:addresses].compact,
459
+ asset_groups: @included_scan_targets[:asset_groups].compact
460
+ }
461
+ excluded_scan_targets = {
462
+ addresses: @excluded_scan_targets[:addresses].compact,
463
+ asset_groups: @excluded_scan_targets[:asset_groups].compact
464
+ }
465
+
466
+ {
467
+ id: @id,
468
+ name: @name,
469
+ description: @description,
470
+ auto_engine_selection_enabled: @auto_engine_selection_enabled,
471
+ included_scan_targets: included_scan_targets,
472
+ excluded_scan_targets: excluded_scan_targets,
473
+ engine_id: @engine_id,
474
+ scan_template_id: @scan_template_id,
475
+ risk_factor: @risk_factor,
476
+ schedules: (@schedules || []).map {|schedule| schedule.to_h},
477
+ shared_credentials: (@shared_credentials || []).map {|cred| cred.to_h},
478
+ site_credentials: (@site_credentials || []).map {|cred| cred.to_h},
479
+ web_credentials: (@web_credentials || []).map {|webCred| webCred.to_h},
480
+ discovery_config: @discovery_config.to_h,
481
+ search_criteria: @search_criteria.to_h,
482
+ tags: (@tags || []).map{|tag| tag.to_h},
483
+ alerts: (@alerts || []).map {|alert| alert.to_h },
484
+ organization: @organization.to_h,
485
+ users: users
486
+ }
487
+ end
488
+
489
+ require 'json'
490
+ # Load an site from the provided console.
491
+ #
492
+ # @param [Connection] nsc Active connection to a Nexpose console.
493
+ # @param [String] id Unique identifier of a site.
494
+ # @return [Site] The requested site, if found.
495
+ #
496
+ def self.load(nsc, id)
497
+ uri = "/api/2.1/site_configurations/#{id}"
498
+ resp = AJAX.get(nsc, uri, AJAX::CONTENT_TYPE::JSON)
499
+ hash = JSON.parse(resp, symbolize_names: true)
500
+ site = self.json_initializer(hash).deserialize(hash)
501
+
502
+ # Convert each string address to either a HostName or IPRange object
503
+ included_addresses = hash[:included_scan_targets][:addresses]
504
+ site.included_scan_targets[:addresses] = []
505
+ included_addresses.each { |asset| site.include_asset(asset) }
506
+
507
+ excluded_addresses = hash[:excluded_scan_targets][:addresses]
508
+ site.excluded_scan_targets[:addresses] = []
509
+ excluded_addresses.each { |asset| site.exclude_asset(asset) }
510
+
511
+ site.organization = Organization.create(site.organization)
512
+ site.schedules = (hash[:schedules] || []).map {|schedule| Nexpose::Schedule.from_hash(schedule) }
513
+ site.site_credentials = hash[:site_credentials].map {|cred| Nexpose::SiteCredentials.new.object_from_hash(nsc,cred)}
514
+ site.shared_credentials = hash[:shared_credentials].map {|cred| Nexpose::SiteCredentials.new.object_from_hash(nsc,cred)}
515
+ site.discovery_config = Nexpose::DiscoveryConnection.new.object_from_hash(nsc, hash[:discovery_config]) unless hash[:discovery_config].nil?
516
+ site.search_criteria = Nexpose::DiscoveryConnection::Criteria.parseHash(hash[:search_criteria]) unless hash[:search_criteria].nil?
517
+ site.alerts = Alert.load_alerts(hash[:alerts])
518
+ site.tags = Tag.load_tags(hash[:tags])
519
+ site.web_credentials = hash[:web_credentials].map {|webCred| (
520
+ webCred[:service] == Nexpose::WebCredentials::WebAppAuthType::HTTP_HEADER ?
521
+ Nexpose::WebCredentials::Headers.new(webCred[:name], webCred[:baseURL], webCred[:soft403Pattern], webCred[:id]).object_from_hash(nsc,webCred) :
522
+ Nexpose::WebCredentials::HTMLForms.new(webCred[:name], webCred[:baseURL], webCred[:loginURL], webCred[:soft403Pattern], webCred[:id]).object_from_hash(nsc,webCred))}
523
+
524
+ site
525
+ end
526
+
527
+ def self.json_initializer(data)
528
+ new(data[:name], data[:scan_template_id])
529
+ end
530
+
313
531
  # Copy an existing configuration from a Nexpose instance.
314
532
  # Returned object will reset the site ID and append "Copy" to the existing
315
533
  # name.
@@ -333,23 +551,21 @@ module Nexpose
333
551
  # @return [Fixnum] Site ID assigned to this configuration, if successful.
334
552
  #
335
553
  def save(connection)
336
- if dynamic?
337
- raise APIError.new(nil, 'Cannot save a dynamic site without a discovery connection configured.') unless @discovery_connection_id
338
-
339
- new_site = @id == -1
340
- save_dynamic_criteria(connection) if new_site
341
-
342
- # Have to retrieve and attach shared creds, or saving will fail.
343
- xml = _append_shared_creds_to_xml(connection, as_xml)
344
- response = AJAX.post(connection, '/data/site/config', xml)
345
- saved = REXML::XPath.first(REXML::Document.new(response), 'ajaxResponse')
346
- raise APIError.new(response, 'Failed to save dynamic site.') if saved.nil? || saved.attributes['success'].to_i != 1
554
+ new_site = @id == -1
347
555
 
348
- save_dynamic_criteria(connection) unless new_site
556
+ if new_site
557
+ resp = AJAX.post(connection, '/api/2.1/site_configurations/', to_json, AJAX::CONTENT_TYPE::JSON)
558
+ @id = resp.to_i
349
559
  else
350
- r = connection.execute('<SiteSaveRequest session-id="' + connection.session_id + '">' + to_xml + ' </SiteSaveRequest>')
351
- @id = r.attributes['site-id'].to_i if r.success
560
+ resp = AJAX.put(connection, "/api/2.1/site_configurations/#{@id}", to_json, AJAX::CONTENT_TYPE::JSON)
352
561
  end
562
+
563
+ # Retrieve the scan engine and shared credentials and add them to the site configuration
564
+ site_config = Site.load(connection, @id)
565
+ @engine_id = site_config.engine_id
566
+ @shared_credentials = site_config.shared_credentials
567
+ @alerts = site_config.alerts
568
+
353
569
  @id
354
570
  end
355
571
 
@@ -378,209 +594,6 @@ module Nexpose
378
594
  response = connection.execute(xml, '1.1', timeout: 60)
379
595
  Scan.parse(response.res) if response.success
380
596
  end
381
-
382
- # Save only the criteria of a dynamic site.
383
- #
384
- # @param [Connection] nsc Connection to a console.
385
- # @return [Fixnum] Site ID.
386
- #
387
- def save_dynamic_criteria(nsc)
388
- # Several parameters are passed through the URI
389
- params = { 'configID' => @discovery_connection_id,
390
- 'entityid' => @id > 0 ? @id : false,
391
- 'mode' => @id > 0 ? 'edit' : false }
392
- uri = AJAX.parameterize_uri('/data/site/saveSite', params)
393
-
394
- # JSON body of POST request contains details.
395
- details = { 'dynamic' => true,
396
- 'name' => @name,
397
- 'tag' => @description.nil? ? '' : @description,
398
- 'riskFactor' => @risk_factor,
399
- # 'vCenter' => @discovery_connection_id,
400
- 'searchCriteria' => @criteria.nil? ? { 'operator' => 'AND' } : @criteria.to_h }
401
- json = JSON.generate(details)
402
-
403
- response = AJAX.post(nsc, uri, json, AJAX::CONTENT_TYPE::JSON)
404
- json = JSON.parse(response)
405
- if json['response'] =~ /success/
406
- if @id < 1
407
- @id = json['entityID'].to_i
408
- end
409
- else
410
- raise APIError.new(response, json['message'])
411
- end
412
- @id
413
- end
414
-
415
- # Retrieve the currrent filter criteria used by a dynamic site.
416
- #
417
- # @param [Connection] nsc Connection to a console.
418
- # @return [Criteria] Current criteria for the site.
419
- #
420
- def load_dynamic_attributes(nsc)
421
- response = AJAX.get(nsc, "/data/site/loadDynamicSite?entityid=#{@id}")
422
- json = JSON.parse(response)
423
- @discovery_connection_id = json['discoveryConfigs']['id']
424
- @criteria = Criteria.parse(json['searchCriteria'])
425
- end
426
-
427
- include Sanitize
428
-
429
- # Generate an XML representation of this site configuration
430
- #
431
- # @return [String] XML valid for submission as part of other requests.
432
- #
433
- def as_xml
434
- xml = REXML::Element.new('Site')
435
- xml.attributes['id'] = @id
436
- xml.attributes['name'] = @name
437
- xml.attributes['description'] = @description
438
- xml.attributes['riskfactor'] = @risk_factor
439
- xml.attributes['isDynamic'] = '1' if dynamic?
440
- # TODO This should be set to 'Amazon Web Services' for AWS.
441
- xml.attributes['dynamicConfigType'] = 'vSphere' if dynamic?
442
-
443
- if @description && !@description.empty?
444
- elem = REXML::Element.new('Description')
445
- elem.add_text(@description)
446
- xml.add_element(elem)
447
- end
448
-
449
- unless @users.empty?
450
- elem = REXML::Element.new('Users')
451
- @users.each { |user| elem.add_element('user', { 'id' => user }) }
452
- xml.add_element(elem)
453
- end
454
-
455
- xml.add_element(@organization.as_xml) if @organization
456
-
457
- elem = REXML::Element.new('Hosts')
458
- @assets.each { |a| elem.add_element(a.as_xml) }
459
- xml.add_element(elem)
460
-
461
- elem = REXML::Element.new('ExcludedHosts')
462
- @exclude.each { |e| elem.add_element(e.as_xml) }
463
- xml.add_element(elem)
464
-
465
- unless credentials.empty?
466
- elem = REXML::Element.new('Credentials')
467
- @credentials.each { |c| elem.add_element(c.as_xml) }
468
- xml.add_element(elem)
469
- end
470
-
471
- unless alerts.empty?
472
- elem = REXML::Element.new('Alerting')
473
- alerts.each { |a| elem.add_element(a.as_xml) }
474
- xml.add_element(elem)
475
- end
476
-
477
- elem = REXML::Element.new('ScanConfig')
478
- elem.add_attributes({ 'configID' => @id,
479
- 'name' => @scan_template_name || @scan_template,
480
- 'templateID' => @scan_template,
481
- 'configVersion' => @config_version || 3,
482
- 'engineID' => @engine })
483
- sched = REXML::Element.new('Schedules')
484
- @schedules.each { |s| sched.add_element(s.as_xml) }
485
- elem.add_element(sched)
486
- xml.add_element(elem)
487
-
488
- unless tags.empty?
489
- tag_xml = xml.add_element(REXML::Element.new('Tags'))
490
- @tags.each { |tag| tag_xml.add_element(tag.as_xml) }
491
- end
492
-
493
- xml
494
- end
495
-
496
- def to_xml
497
- as_xml.to_s
498
- end
499
-
500
- # Parse a response from a Nexpose console into a valid Site object.
501
- #
502
- # @param [REXML::Document] rexml XML document to parse.
503
- # @return [Site] Site object represented by the XML.
504
- # ## TODO What is returned on failure?
505
- #
506
- def self.parse(rexml)
507
- rexml.elements.each('//Site') do |s|
508
- site = Site.new(s.attributes['name'])
509
- site.id = s.attributes['id'].to_i
510
- site.description = s.attributes['description']
511
- site.risk_factor = s.attributes['riskfactor'] || 1.0
512
- site.is_dynamic = true if s.attributes['isDynamic'] == '1'
513
-
514
- s.elements.each('Description') do |desc|
515
- site.description = desc.text
516
- end
517
-
518
- s.elements.each('Users/user') do |user|
519
- site.users << user.attributes['id'].to_i
520
- end
521
-
522
- s.elements.each('Organization') do |org|
523
- site.organization = Organization.parse(org)
524
- end
525
-
526
- s.elements.each('Hosts/range') do |r|
527
- site.assets << IPRange.new(r.attributes['from'], r.attributes['to'])
528
- end
529
- s.elements.each('Hosts/host') do |host|
530
- site.assets << HostName.new(host.text)
531
- end
532
-
533
- s.elements.each('ExcludedHosts/range') do |r|
534
- site.exclude << IPRange.new(r.attributes['from'], r.attributes['to'])
535
- end
536
- s.elements.each('ExcludedHosts/host') do |host|
537
- site.exclude << HostName.new(host.text)
538
- end
539
-
540
- s.elements.each('Credentials/adminCredentials') do |cred|
541
- site.credentials << SiteCredential.parse(cred)
542
- end
543
-
544
- s.elements.each('ScanConfig') do |scan_config|
545
- site.scan_template_name = scan_config.attributes['name']
546
- site.scan_template = scan_config.attributes['templateID']
547
- site.config_version = scan_config.attributes['configVersion'].to_i
548
- site.engine = scan_config.attributes['engineID'].to_i
549
- scan_config.elements.each('Schedules/Schedule') do |schedule|
550
- site.schedules << Schedule.parse(schedule)
551
- end
552
- end
553
-
554
- s.elements.each('Alerting/Alert') do |alert|
555
- site.alerts << Alert.parse(alert)
556
- end
557
-
558
- s.elements.each('Tags/Tag') do |tag|
559
- site.tags << TagSummary.parse_xml(tag)
560
- end
561
-
562
- return site
563
- end
564
- nil
565
- end
566
-
567
- def _append_shared_creds_to_xml(connection, xml)
568
- xml_w_creds = AJAX.get(connection, "/data/site/config?siteid=#{@id}")
569
- cred_xml = REXML::XPath.first(REXML::Document.new(xml_w_creds), 'Site/Credentials')
570
- unless cred_xml.nil?
571
- creds = REXML::XPath.first(xml, 'Credentials')
572
- if creds.nil?
573
- xml.add_element(cred_xml)
574
- else
575
- cred_xml.elements.each do |cred|
576
- if cred.attributes['shared'].to_i == 1
577
- creds.add_element(cred)
578
- end
579
- end
580
- end
581
- end
582
- xml
583
- end
584
597
  end
585
598
 
586
599
  # Object that represents the summary of a Nexpose Site.
@@ -642,6 +655,10 @@ module Nexpose
642
655
  def to_xml
643
656
  to_xml_elem.to_s
644
657
  end
658
+
659
+ def to_s
660
+ @host.to_s
661
+ end
645
662
  end
646
663
 
647
664
  # Object that represents a single IP address or an inclusive range of IP addresses.
@@ -752,5 +769,10 @@ module Nexpose
752
769
  def to_xml
753
770
  as_xml.to_s
754
771
  end
772
+
773
+ def to_s
774
+ return from.to_s if to.nil?
775
+ "#{from.to_s} - #{to.to_s}"
776
+ end
755
777
  end
756
778
  end