controls 1.7.0.beta.1 → 1.7.0

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