nexpose 0.9.8 → 1.0.0

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