controls 1.7.0.beta.1 → 1.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aa017f4f3585d9259d937543ea48e9491d59954d
4
- data.tar.gz: abe2acf9f7704682b07cef385dba35e3e59aaeae
3
+ metadata.gz: 5c68ebb70f4ca7eb870f510d30c18ab95cad9c44
4
+ data.tar.gz: 72f79ad7b6814653b8e1b2ac9e1fa75cdaa2bfdb
5
5
  SHA512:
6
- metadata.gz: 42d0bc45400ab1278e705ff15b097dc0106e139c5eb3e855bf8250809f6bde5d250618d07a5d903a7a73908794aaa24574ba767eb112e346414bd655be041010
7
- data.tar.gz: 2793dcd70a0dab72a895485694245356a6eeaf5b6ab37edc84e6f7ff27fa8d6cf51f2a97b835cdc21e71e0cf737030a4a8c8f13f006d5744e75fb3251cd8650f
6
+ metadata.gz: 7189f9747d18096cbd4eacff03f2b0ddbfaf8ce2ead46364b8406fda8778315b74a6bbe51ca779d99bdc25022382788bc1b548093ee9dded5e599a4c6f5f54da
7
+ data.tar.gz: df937e63a59fc5bed775f39ea0c58f1b369ec3218778248c893a470a844e47cc8374e69369a3ea960e857945dba50a9df8f24e9d27d65320a0c7014a419e42db
data/.yardopts CHANGED
@@ -1 +1,2 @@
1
1
  - LICENSE.md
2
+ --exclude=lib/controls/ext
data/lib/controls.rb CHANGED
@@ -3,8 +3,6 @@ require 'controls/default'
3
3
 
4
4
  # A Ruby client for the **controls**insight API
5
5
  module Controls
6
- Error = Class.new(StandardError)
7
-
8
6
  class << self
9
7
  include Controls::Configurable
10
8
 
@@ -20,11 +18,19 @@ module Controls
20
18
  @client
21
19
  end
22
20
 
21
+ # Yields the global client to configure in a block
22
+ #
23
23
  # @yield [client]
24
24
  def configure
25
25
  yield client
26
26
  end
27
27
 
28
+ # Overrides the respond_to_missing method to act as a proxy for
29
+ # {Controls::Client}
30
+ #
31
+ # @param [Symbol,String] method_name the method name to check for
32
+ # @param [Boolean] include_private to include private methods when checking for method response
33
+ # @return [Boolean] whether {Controls} responds to the method
28
34
  def respond_to_missing?(method_name, include_private = false)
29
35
  client.respond_to?(method_name, include_private)
30
36
  end
@@ -216,6 +216,10 @@ module Controls
216
216
  end
217
217
  end
218
218
 
219
+ # Creates an error from the last request
220
+ #
221
+ # @param [String] message the message to prepend to the response code/status
222
+ # @return [Controls::Error] the generated error message
219
223
  def exception(message = "HTTP Error")
220
224
  last_request = _last_request
221
225
  if last_request
@@ -1,6 +1,14 @@
1
1
  module Controls
2
2
  class Client
3
+ # A module to encapsulate API methods related to coverage
4
+ # @since API v1.0
5
+ # [todo] - this version is obviously off
6
+ # @version v1.0.0
3
7
  module Coverage
8
+ # Either returns coverage for all security controls or one by name
9
+ #
10
+ # @param [String] security_control_name the security control to return coverage for
11
+ # @return [Array<Controls::SecurityControlCoverage>,Controls::SecurityControlCoverage]
4
12
  def security_control_coverage(security_control_name = nil)
5
13
  if security_control_name
6
14
  get "/coverage/security_controls/#{security_control_name}"
@@ -2,7 +2,8 @@ module Controls
2
2
  class Client
3
3
  # A module to encapsulate API methods related to assets
4
4
  # @since API v1.0
5
- # @version v1.0.0
5
+ # [todo] - this version is obviously off
6
+ # @version v1.7.0
6
7
  module Findings
7
8
  # @!group Security Control/Configuration Findings Methods
8
9
 
@@ -33,7 +33,7 @@ module Controls
33
33
 
34
34
  # Updates the specified security control(s)
35
35
  #
36
- # @param [Array[Hash{String=>String,Boolean}] controls a list of controls to update
36
+ # @param [Array[Hash{String=>String,Boolean}]] controls a list of controls to update
37
37
  # @return [void]
38
38
  def update_security_controls(controls)
39
39
  # [review] - this style is a discouraged for Arrays, but we want to treat controls as an Array of Hashes
@@ -1,25 +1,5 @@
1
1
  module Dish
2
2
  class Plate
3
- def method_missing(method, *args, &block)
4
- method = method.to_s
5
- camel_case_key = method.split('_').map(&:capitalize).join
6
- camel_case_key[0] = camel_case_key[0].downcase
7
-
8
- if method.end_with? '?'
9
- key = camel_case_key[0..-2]
10
- _check_for_presence(key)
11
- elsif method.end_with? '='
12
- key = camel_case_key[0..-2]
13
- _set_value(key, args.first)
14
- else
15
- if @_original_hash.key?(camel_case_key)
16
- _get_value(camel_case_key)
17
- else
18
- super(method.to_sym, *args, &block)
19
- end
20
- end
21
- end
22
-
23
3
  def methods(all = true)
24
4
  valid_keys = as_hash.keys.map do |key|
25
5
  key.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase.to_sym
@@ -53,5 +33,25 @@ module Dish
53
33
  value = _convert_value(value, self.class.coercions[key])
54
34
  @_original_hash[key] = value
55
35
  end
36
+
37
+ def method_missing(method, *args, &block)
38
+ method = method.to_s
39
+ camel_case_key = method.split('_').map(&:capitalize).join
40
+ camel_case_key[0] = camel_case_key[0].downcase
41
+
42
+ if method.end_with? '?'
43
+ key = camel_case_key[0..-2]
44
+ _check_for_presence(key)
45
+ elsif method.end_with? '='
46
+ key = camel_case_key[0..-2]
47
+ _set_value(key, args.first)
48
+ else
49
+ if @_original_hash.key?(camel_case_key)
50
+ _get_value(camel_case_key)
51
+ else
52
+ super(method.to_sym, *args, &block)
53
+ end
54
+ end
55
+ end
56
56
  end
57
57
  end
@@ -1,7 +1,9 @@
1
1
  module Controls
2
+ # A representation of the Assessment resource
2
3
  class Assessment < Dish::Plate
3
4
  coerce :timestamp, ->(value) { Time.at(value / 1000) if value }
4
5
 
6
+ # The assessment ID as a string
5
7
  def to_s
6
8
  id.to_s
7
9
  end
@@ -1,6 +1,7 @@
1
1
  require 'controls/objects/security_control_finding'
2
2
 
3
3
  module Controls
4
+ # A representation of the Asset resource
4
5
  class Asset < Dish::Plate
5
6
  coerce :discoveredAt, ->(value) { Time.at(value / 1000) if value }
6
7
 
@@ -8,30 +9,57 @@ module Controls
8
9
  Controls.client.findings_by_asset_uuid(uuid)
9
10
  end
10
11
 
12
+ # Returns the hostname, IP, and OS of the asset
13
+ #
14
+ # @example
15
+ # "jdoe.local (192.168.1.23) - Windows 7 Professional Edition"
16
+ #
17
+ # @return [String]
11
18
  def to_s
12
19
  %(#{host_name} (#{ipaddress}) - #{operating_system})
13
20
  end
14
21
  end
15
22
 
23
+ # A collection of Asset resources
16
24
  class AssetCollection < Dish::Plate
17
25
  coerce :resources, Asset
18
26
 
19
- def map(*args, &block)
27
+ # [todo] - metaprogram any proxy methods?
28
+ # - be sure to define method_missing AND respond_to_missing?
29
+
30
+ # Acts as a proxy to resources.map
31
+ #
32
+ # @yield [resource] gives three resources
33
+ def map(&block)
20
34
  resources.map(*args, &block)
21
35
  end
22
36
 
37
+ # Acts as a proxy to resources.first
38
+ #
39
+ # @return [Controls::Asset]
23
40
  def first
24
41
  resources.first
25
42
  end
26
43
 
44
+ # Acts as a proxy to resources.last
45
+ #
46
+ # @return [Controls::Asset] the last asset in the
47
+ # {Controls::AssetCollection}
27
48
  def last
28
49
  resources.last
29
50
  end
30
51
 
52
+ # Acts as a proxy to resources.[]
53
+ #
54
+ # @param [Fixnum] index the index of the asset to fetch
55
+ # @return [Controls::Asset] the asset by index
31
56
  def [](index)
32
57
  resources[index]
33
58
  end
34
59
 
60
+ # Returns a comma separated list of IP addresses
61
+ #
62
+ # @return [String]
35
63
  def to_s
36
64
  resources.sort_by(&:ipaddress).map(&:to_s).join("\n")
37
65
  end
@@ -1,10 +1,14 @@
1
1
  require 'controls/objects/coverage_information'
2
2
 
3
3
  module Controls
4
+ # A representation of the Configuration resource w/ coverage information
4
5
  class Configuration < Dish::Plate
5
6
  coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
6
7
  coerce :coverage, Controls::CoverageInformation
7
8
 
9
+ # The title of the configuration
10
+ #
11
+ # @return [String]
8
12
  def to_s
9
13
  title
10
14
  end
@@ -1,5 +1,11 @@
1
1
  module Controls
2
+ # A representation of the ConfigurationFinding resource
2
3
  class ConfigurationFinding < Dish::Plate
4
+ coerce :reason, ->(value) { value.strip! }
5
+
6
+ # Returns the name, state, and reason data for the configuration finding
7
+ #
8
+ # "antivirus-installed: state: TRUE reason: Endpoint Security installed"
3
9
  def to_s
4
10
  "#{name}: state: #{state} reason: #{reason.strip}"
5
11
  end
@@ -1,7 +1,7 @@
1
1
  module Controls
2
+ # A representation of the CoverageInformation for SecurityControl or
3
+ # Configuration coverage
2
4
  class CoverageInformation < Dish::Plate
3
- def to_s
4
- "#<#{self.class}: total: #{total}, covered: #{covered}, uncovered: #{uncovered}, percent_covered: #{percent_covered}>"
5
- end
5
+ alias_method :to_s, :inspect
6
6
  end
7
7
  end
@@ -0,0 +1,12 @@
1
+ module Controls
2
+ # A class under the Controls namespace to wrap API errors
3
+ class Error < StandardError
4
+ # @!attribute message
5
+ # The message related to the error
6
+ attr_accessor :message
7
+
8
+ # @!attribute message
9
+ # The status code for the error response
10
+ attr_accessor :status
11
+ end
12
+ end
@@ -1,16 +1,29 @@
1
1
  module Controls
2
+ # A representation of the Event resource
2
3
  class Event < Dish::Plate
3
4
  coerce :createdAt, ->(value) { Time.at(value / 1000) if value }
4
5
 
6
+ # Coerces the payload into the appropriate type
7
+ #
8
+ # @return [Controls::SecurityControlChangeEventPayload,Controls::SiteChangeEventPayload,Controls::ProductEventPayload]
9
+ # the payload respective of the event type
5
10
  def payload
6
11
  value = _get_value('payload')
7
12
  Dish(value, Controls.const_get("#{type}Payload"))
8
13
  end
9
14
 
15
+ # Overrides #inspect to use the proper event type
16
+ #
17
+ # @return [String] the result of super with the corrected event type
10
18
  def inspect
11
19
  super.sub('Event', type)
12
20
  end
13
21
 
22
+ # Returns the event type
23
+ #
24
+ # [todo] - is the type all we want to return?
25
+ #
26
+ # @return [string] the event type
14
27
  def to_s
15
28
  type
16
29
  end
@@ -1,4 +1,4 @@
1
1
  module Controls
2
- class ProductChangeEventPayload < Dish::Plate
3
- end
2
+ # A representation of a given ProductChangeEvent's payload
3
+ ProductChangeEventPayload = Class.new(Dish::Plate)
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module Controls
2
- class SecurityControlChangeEventPayload < Dish::Plate
3
- end
2
+ # A representation of a given SecurityControlChangeEvent's payload
3
+ SecurityControlChangeEventPayload = Class.new(Dish::Plate)
4
4
  end
@@ -1,4 +1,4 @@
1
1
  module Controls
2
- class SiteChangeEventPayload < Dish::Plate
3
- end
2
+ # A representation of a given SiteChangeEventPayload's payload
3
+ SiteChangeEventPayload = Class.new(Dish::Plate)
4
4
  end
@@ -3,11 +3,20 @@ require 'controls/objects'
3
3
  module Controls
4
4
  # A module to encapsulate middleware customization
5
5
  module Response
6
- def self.parse(obj, path = nil)
6
+ module_function
7
+
8
+ # Returns the parsed JSON response after marshaling through Dish
9
+ #
10
+ # @param [String] obj the JSON object to parse
11
+ # @param [String] path the requested API endpoint's path
12
+ # @return [Dish::Plate,Object] a marshaled representation of the JSON response
13
+ def parse(obj, path = nil)
7
14
  hash_or_array = JSON.parse(obj)
8
15
 
9
16
  if hash_or_array.is_a?(Hash) && hash_or_array.key?('message') && hash_or_array.key?('documentationUrl')
10
17
  type = Controls::Error
18
+ elsif hash_or_array.is_a?(Hash) && hash_or_array.key?('message') && hash_or_array.key?('status')
19
+ type = Controls::Error
11
20
  end
12
21
 
13
22
  type ||=
@@ -1,4 +1,4 @@
1
1
  module Controls
2
2
  # The version of the Controls gem
3
- VERSION = '1.7.0.beta.1'
3
+ VERSION = '1.7.0'
4
4
  end
@@ -7,7 +7,9 @@ describe '/api/assessments' do
7
7
  it 'returns a list of assessments' do
8
8
  assessments = Controls.assessments
9
9
 
10
- expect(assessments).to match_assessment_format
10
+ assessments.each do |assessment|
11
+ expect(assessment).to match_assessment_format
12
+ end
11
13
  end
12
14
  end
13
15
 
data/spec/matchers.rb CHANGED
@@ -4,43 +4,18 @@ require 'rspec/expectations'
4
4
  RSpec::Matchers.define :match_assessment_format do
5
5
  match do |resource|
6
6
  # Reverses the coercion
7
- resource.timestamp = resource.timestamp.to_i
8
7
  [
9
8
  resource.high_risk_asset_count,
10
9
  resource.id,
11
10
  resource.low_risk_asset_count,
12
11
  resource.medium_risk_asset_count,
13
- resource.timestamp,
12
+ resource.timestamp.to_i,
14
13
  resource.total_asset_count
15
14
  ].each do |attribute|
16
15
  expect(attribute.class).to eq(Fixnum)
17
16
  end
18
17
 
19
- expect(resource.assessing.class).to include([TrueClass, FalseClass])
20
- expect(resource.overall_risk_score.class).to include([Float])
18
+ expect([TrueClass, FalseClass].include?(resource.assessing.class)).to be_true
19
+ expect([Float].include?(resource.overall_risk_score.class)).to be_true
21
20
  end
22
21
  end
23
-
24
- #RSpec::Matchers.define :match_event_format do
25
- # [Fixnum].include? resource.createdAt
26
- # [Hash].include? resource.payload
27
- # [String].include? resource.type
28
- # [String].include? resource.user
29
- #end
30
- #
31
- #RSpec::Matchers.define :match_site_change_event_payload_format do
32
- # [TrueClass,FalseClass].include? resource.impactsGrade.class
33
- # [String].include? resource.notes.class
34
- # [String].include? resource.productVersion.class
35
- #end
36
- #
37
- #RSpec::Matchers.define :match_security_control_change_event_payload_format do
38
- # [String,NilClass].include?resource.reason
39
- # [String]}]].include?resource.changes:[Array,[Hash,{securityControlName:[String],action
40
- #end
41
- #
42
- #RSpec::Matchers.define :match__change_event_payload_format do
43
- # [String,NilClass].include?resource.reason
44
- # [Fixnum]}]].include?resource.enabledSites:[Array,[Hash,{name:[String],id
45
- # [TrueClass.include?resource.importAllFalseClass]
46
- #end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: controls
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0.beta.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erran Carey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-04 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dish
@@ -121,6 +121,7 @@ files:
121
121
  - lib/controls/objects/configuration.rb
122
122
  - lib/controls/objects/configuration_finding.rb
123
123
  - lib/controls/objects/coverage_information.rb
124
+ - lib/controls/objects/error.rb
124
125
  - lib/controls/objects/event.rb
125
126
  - lib/controls/objects/guidance.rb
126
127
  - lib/controls/objects/product_change_event_payload.rb
@@ -135,7 +136,6 @@ files:
135
136
  - lib/controls/response.rb
136
137
  - lib/controls/version.rb
137
138
  - spec/controls/client/assessments_spec.rb
138
- - spec/controls/client/events_spec.rb
139
139
  - spec/matchers.rb
140
140
  - spec/spec_helper.rb
141
141
  homepage: ''
@@ -153,9 +153,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
153
  version: '0'
154
154
  required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  requirements:
156
- - - ">"
156
+ - - ">="
157
157
  - !ruby/object:Gem::Version
158
- version: 1.3.1
158
+ version: '0'
159
159
  requirements: []
160
160
  rubyforge_project:
161
161
  rubygems_version: 2.2.0
@@ -164,7 +164,6 @@ specification_version: 4
164
164
  summary: This gem interfaces to Rapid7's **controls**insight API.
165
165
  test_files:
166
166
  - spec/controls/client/assessments_spec.rb
167
- - spec/controls/client/events_spec.rb
168
167
  - spec/matchers.rb
169
168
  - spec/spec_helper.rb
170
169
  has_rdoc:
@@ -1,54 +0,0 @@
1
- #describe '/api/events' do
2
- # before do
3
- # login_to_environment
4
- # end
5
- #
6
- # context 'GET /api/events' do
7
- # it 'returns a list of events' do
8
- # events = Controls.events
9
- #
10
- # expect(events).to match_format(event_format)
11
- # end
12
- # end
13
- #
14
- # context 'GET /api/events?filter=ProductChangeEvent' do
15
- # it 'returns a list of events' do
16
- # events = Controls.events filter: 'ProductChangeEvent'
17
- #
18
- # expect(events).to match_format(event_format)
19
- # expect(events.map(&:payload)).to match_format(product_change_format)
20
- # end
21
- # end
22
- #
23
- # context 'GET /api/events?filter=SecurityControlChangeEvent' do
24
- # it 'returns a list of events' do
25
- # events = Controls.events filter: 'SecurityControlChangeEvent'
26
- # expected_payload_format = security_control_change_format
27
- #
28
- # expect(events).to match_format(event_format)
29
- # expect(events.map(&:payload)).to match_format(expected_payload_format)
30
- #
31
- # events.map(&:payload).each do |payload|
32
- # payload.keys.map(&:to_sym).each do |key|
33
- # expect(payload.send(key)).to match_format(expected_payload_format[key].last)
34
- # end
35
- # end
36
- # end
37
- # end
38
- #
39
- # context 'GET /api/events?filter=SiteChangeEvent' do
40
- # it 'returns a list of events' do
41
- # events = Controls.events filter: 'SiteChangeEvent'
42
- # expected_payload_format = site_change_format
43
- #
44
- # expect(events).to match_format(event_format)
45
- # expect(events.map(&:payload)).to match_format(expected_payload_format)
46
- #
47
- # events.map(&:payload).each do |payload|
48
- # payload.keys.map(&:to_sym).each do |key|
49
- # expect(payload.send(key)).to match_format(expected_payload_format[key].last)
50
- # end
51
- # end
52
- # end
53
- # end
54
- #end